defmodule Mobilizon.Service.ActivityPub.Converters.Event do @moduledoc """ Event converter This module allows to convert events from ActivityStream format to our own internal one, and back """ alias Mobilizon.Actors alias Mobilizon.Media alias Mobilizon.Media.Picture alias Mobilizon.Actors.Actor alias Mobilizon.Events.Event, as: EventModel alias Mobilizon.Service.ActivityPub.Converter alias Mobilizon.Service.ActivityPub.Converters.Address, as: AddressConverter alias Mobilizon.Service.ActivityPub.Utils alias Mobilizon.Events alias Mobilizon.Events.Tag alias Mobilizon.Addresses alias Mobilizon.Addresses.Address @behaviour Converter require Logger @doc """ Converts an AP object data to our internal data structure """ @impl Converter @spec as_to_model_data(map()) :: map() def as_to_model_data(object) do Logger.debug("event as_to_model_data") Logger.debug(inspect(object)) with {:actor, {:ok, %Actor{id: actor_id}}} <- {:actor, Actors.get_actor_by_url(object["actor"])}, {:address, address_id} <- {:address, get_address(object["location"])}, {:tags, tags} <- {:tags, fetch_tags(object["tag"])}, {:options, options} <- {:options, get_options(object)} do picture_id = with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0, %Picture{id: picture_id} <- Media.get_picture_by_url( object["attachment"] |> hd |> Map.get("url") |> hd |> Map.get("href") ) do picture_id else _ -> nil end entity = %{ "title" => object["name"], "description" => object["content"], "organizer_actor_id" => actor_id, "picture_id" => picture_id, "begins_on" => object["startTime"], "ends_on" => object["endTime"], "category" => object["category"], "join_options" => object["joinOptions"], "url" => object["id"], "uuid" => object["uuid"], "tags" => tags, "physical_address_id" => address_id } {:ok, Map.put(entity, "options", options)} else err -> {:error, err} end end # Get only elements that we have in EventOptions defp get_options(object) do keys = Mobilizon.Events.EventOptions |> struct |> Map.keys() |> List.delete(:__struct__) |> Enum.map(&Utils.camelize/1) Enum.reduce(object, %{}, fn {key, value}, acc -> (value && key in keys && Map.put(acc, Utils.underscore(key), value)) || acc end) end defp get_address(address_url) when is_bitstring(address_url) do get_address(%{"id" => address_url}) end defp get_address(%{"id" => url} = map) when is_map(map) and is_binary(url) do Logger.debug("Address with an URL, let's check against our own database") case Addresses.get_address_by_url(url) do %Address{id: address_id} -> address_id _ -> Logger.debug("not in our database, let's try to create it") map = Map.put(map, "url", map["id"]) do_get_address(map) end end defp get_address(map) when is_map(map) do do_get_address(map) end defp get_address(nil), do: nil defp do_get_address(map) do map = Mobilizon.Service.ActivityPub.Converters.Address.as_to_model_data(map) case Addresses.create_address(map) do {:ok, %Address{id: address_id}} -> address_id _ -> nil end end defp fetch_tags(tags) do Logger.debug("fetching tags") Enum.reduce(tags, [], fn tag, acc -> with true <- tag["type"] == "Hashtag", {:ok, %Tag{} = tag} <- Events.get_or_create_tag(tag) do acc ++ [tag] else _err -> acc end end) end defp build_tags(tags) do Enum.map(tags, fn %Tag{} = tag -> %{ "href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag.slug}", "name" => "##{tag.title}", "type" => "Hashtag" } end) end @doc """ Convert an event struct to an ActivityStream representation """ @impl Converter @spec model_to_as(EventModel.t()) :: map() def model_to_as(%EventModel{} = event) do to = if event.visibility == :public, do: ["https://www.w3.org/ns/activitystreams#Public"], else: [event.organizer_actor.followers_url] res = %{ "type" => "Event", "to" => to, "cc" => [], "attributedTo" => event.organizer_actor.url, "name" => event.title, "actor" => event.organizer_actor.url, "uuid" => event.uuid, "category" => event.category, "content" => event.description, "publish_at" => (event.publish_at || event.inserted_at) |> date_to_string(), "updated_at" => event.updated_at |> date_to_string(), "mediaType" => "text/html", "startTime" => event.begins_on |> date_to_string(), "endTime" => event.ends_on |> date_to_string(), "tag" => event.tags |> build_tags(), "id" => event.url, "url" => event.url } res = if is_nil(event.physical_address), do: res, else: Map.put(res, "location", AddressConverter.model_to_as(event.physical_address)) if is_nil(event.picture), do: res, else: Map.put(res, "attachment", [Utils.make_picture_data(event.picture)]) end defp date_to_string(nil), do: nil defp date_to_string(date), do: DateTime.to_iso8601(date) end defimpl Mobilizon.Service.ActivityPub.Convertible, for: Mobilizon.Events.Event do alias Mobilizon.Service.ActivityPub.Converters.Event, as: EventConverter defdelegate model_to_as(event), to: EventConverter end