Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2022-04-24 17:57:56 +02:00
parent b3e7f23604
commit a544e6aee7
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
7 changed files with 193 additions and 12 deletions

View File

@ -248,6 +248,11 @@ config :mobilizon, :maps,
type: :openstreetmap
]
config :mobilizon, Mobilizon.Service.Search, extra_provider: Mobilizon.Service.Search.SearchIndex
config :mobilizon, Mobilizon.Service.Search.SearchIndex,
endpoint: "https://search.joinmobilizon.org"
config :mobilizon, :http_security,
enabled: true,
sts: false,

View File

@ -74,7 +74,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
medias: medias,
begins_on: object["startTime"],
ends_on: object["endTime"],
category: get_category(object["category"]),
category: Categories.get_category(object["category"]),
visibility: visibility,
join_options: Map.get(object, "joinMode", "free"),
local: is_local?(object["id"]),
@ -331,15 +331,4 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
_participant_count
),
do: nil
@spec get_category(String.t() | nil) :: String.t()
defp get_category(nil), do: "MEETING"
defp get_category(category) when is_binary(category) do
if category in Enum.map(Categories.list(), &String.upcase(to_string(&1.id))) do
category
else
get_category(nil)
end
end
end

View File

@ -81,6 +81,8 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
description: "Radius around the location to search in"
)
arg(:external, :boolean, default_value: false, description: "Also return external results")
arg(:page, :integer, default_value: 1, description: "Result page")
arg(:limit, :integer, default_value: 10, description: "Results limit per page")
@ -100,6 +102,7 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
description: "Radius around the location to search in"
)
arg(:external, :boolean, default_value: false, description: "Also return external results")
arg(:page, :integer, default_value: 1, description: "Result page")
arg(:limit, :integer, default_value: 10, description: "Results limit per page")
arg(:begins_on, :datetime, description: "Filter events by their start date")

View File

@ -9,6 +9,20 @@ defmodule Mobilizon.Events.Categories do
build_in_categories() ++ extra_categories()
end
@doc """
Get a category for an input string
"""
@spec get_category(String.t() | nil) :: String.t()
def get_category(nil), do: "MEETING"
def get_category(category) when is_binary(category) do
if category in Enum.map(list(), &String.upcase(to_string(&1.id))) do
category
else
get_category(nil)
end
end
defp build_in_categories do
[
%{

View File

@ -0,0 +1,19 @@
defmodule Mobilizon.Service.Search.External do
@moduledoc """
Search providers manager
"""
@doc """
Queries the external search provider
"""
def search(options) do
provider().search(options)
end
@spec provider :: module()
defp provider do
:mobilizon
|> Application.get_env(Mobilizon.Service.Search, [])
|> Keyword.get(:extra_provider, Mobilizon.Service.Search.SearchIndex)
end
end

View File

@ -0,0 +1,10 @@
defmodule Mobilizon.Service.Search.Provider do
@moduledoc """
Behaviour for a search provider
"""
@doc """
Returns a paginated list of events
"""
@callback search(options :: Keyword.t()) :: Mobilizon.Storage.Page.t(Mobilizon.Events.Event.t())
end

View File

@ -0,0 +1,141 @@
defmodule Mobilizon.Service.Search.SearchIndex do
@moduledoc """
Search provider for https://search.joinmobilizon.org
"""
alias Mobilizon.Addresses.Address
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.{Categories, Event, EventOptions}
alias Mobilizon.Service.HTTP.GeospatialClient
alias Mobilizon.Service.Search.Provider
alias Mobilizon.Storage.Page
import Plug.Conn.Query, only: [encode: 1]
@behaviour Provider
@default_endpoint "https://search.joinmobilizon.org"
@events_api_endpoint "/api/v1/search/events"
@groups_api_endpoint "/api/v1/search/groups"
@default_options [
start: 0,
count: 10,
distance: "10_km"
]
# "?search=test&startDateMin=2022-04-21T16:08:32.675Z&boostLanguages[]=fr&boostLanguages[]=en&distance=10_km&sort=-match&start=0&count=5"
@doc """
Returns a paginated list of events
"""
@impl Provider
def search(options) do
case fetch(options) do
{:ok, %Tesla.Env{body: body, status: 200}} ->
{:ok, transform_results(body, Keyword.get(options, :type, :events))}
err ->
require Logger
Logger.error(inspect(err))
{:error, :http_error}
end
end
defp fetch(options) do
@default_options
|> Keyword.merge(options)
|> build_url()
|> GeospatialClient.get()
end
@spec transform_results(%{String.t() => list(map()) | non_neg_integer()}, atom()) ::
Page.t(Event.t() | Actor.t())
defp transform_results(%{"data" => data, "total" => total}, type) do
%Page{
total: total,
elements:
data
|> Enum.sort(&(&1["score"] >= &2["score"]))
|> Enum.map(fn element -> transform_result(element, type) end)
}
end
@spec transform_result(map(), :events | :groups) :: Event.t() | Actor.t()
defp transform_result(event, :events) do
%Event{
category: Categories.get_category(event["category"]),
organizer_actor: transform_actor(event["creator"]),
ends_on: event["endTime"],
attributed_to: transform_actor(event["group"], :Group),
id: event["id"],
join_options: event["joinMode"],
options: %EventOptions{
is_online: event["isOnline"],
maximum_attendee_capacity: event["maximumAttendeeCapacity"]
},
physical_address: transform_address(event["location"]),
title: event["name"],
publish_at: event["published"],
begins_on: event["startTime"],
status: String.downcase(event["status"]),
tags: event["tags"],
uuid: event["uuid"],
url: event["url"]
}
end
defp transform_result(group, :groups) do
group = transform_actor(group)
%Actor{
group
| type: :Group,
physical_address: transform_address(group["location"])
}
end
defp transform_actor(actor, type \\ :Person) do
%Actor{
type: type,
id: actor["id"],
url: actor["url"],
summary: actor["description"],
preferred_username: actor["name"],
name: actor["displayName"],
domain: actor["host"]
}
end
defp transform_address(address) when is_map(address) do
%Address{
description: address["name"],
geom: %Geo.Point{
coordinates: {address["location"]["lon"], address["location"]["lat"]},
srid: 4326
},
country: address["address"]["addressCountry"],
locality: address["address"]["addressLocality"],
region: address["address"]["addressRegion"],
postal_code: address["address"]["postalCode"],
street: address["address"]["streetAddress"]
}
end
defp transform_address(_), do: nil
@spec endpoint :: String.t()
defp endpoint do
:mobilizon
|> Application.get_env(__MODULE__, [])
|> Keyword.get(:endpoint, @default_endpoint)
end
defp build_url(options) do
{type, options} = Keyword.pop(options, :type, :events)
"#{endpoint()}#{api_endpoint(type)}?#{encode(options)}"
end
defp api_endpoint(:events), do: @events_api_endpoint
defp api_endpoint(:groups), do: @groups_api_endpoint
end