Add address input and refactor federation stuff

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-07-30 10:35:29 +02:00
parent bcfc26ee59
commit 5fbaf42cad
34 changed files with 729 additions and 192 deletions

View File

@@ -70,10 +70,11 @@ defmodule Mobilizon.Service.ActivityPub do
def fetch_object_from_url(url) do
Logger.info("Fetching object from url #{url}")
with true <- String.starts_with?(url, "http"),
nil <- Events.get_event_by_url(url),
nil <- Events.get_comment_from_url(url),
{:error, :actor_not_found} <- Actors.get_actor_by_url(url),
with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")},
{:existing_event, nil} <- {:existing_event, Events.get_event_by_url(url)},
{:existing_comment, nil} <- {:existing_comment, Events.get_comment_from_url(url)},
{:existing_actor, {:error, :actor_not_found}} <-
{:existing_actor, Actors.get_actor_by_url(url)},
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
HTTPoison.get(
url,
@@ -90,25 +91,32 @@ defmodule Mobilizon.Service.ActivityPub do
"actor" => data["attributedTo"],
"object" => data
},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, _activity, %{url: object_url} = _object} <- Transmogrifier.handle_incoming(params) do
case data["type"] do
"Event" ->
{:ok, Events.get_event_by_url!(activity.data["object"]["id"])}
{:ok, Events.get_event_by_url!(object_url)}
"Note" ->
{:ok, Events.get_comment_full_from_url!(activity.data["object"]["id"])}
{:ok, Events.get_comment_full_from_url!(object_url)}
"Actor" ->
{:ok, Actors.get_actor_by_url!(activity.data["object"]["id"], true)}
{:ok, Actors.get_actor_by_url!(object_url, true)}
other ->
{:error, other}
end
else
%Event{url: event_url} -> {:ok, Events.get_event_by_url!(event_url)}
%Comment{url: comment_url} -> {:ok, Events.get_comment_full_from_url!(comment_url)}
%Actor{url: actor_url} -> {:ok, Actors.get_actor_by_url!(actor_url, true)}
e -> {:error, e}
{:existing_event, %Event{url: event_url}} ->
{:ok, Events.get_event_by_url!(event_url)}
{:existing_comment, %Comment{url: comment_url}} ->
{:ok, Events.get_comment_full_from_url!(comment_url)}
{:existing_actor, %Actor{url: actor_url}} ->
{:ok, Actors.get_actor_by_url!(actor_url, true)}
e ->
{:error, e}
end
end
@@ -130,10 +138,10 @@ defmodule Mobilizon.Service.ActivityPub do
additional
),
:ok <- Logger.debug(inspect(create_data)),
{:ok, activity, _object} <- insert(create_data, local),
{:ok, activity, object} <- insert(create_data, local),
:ok <- maybe_federate(activity) do
# {:ok, actor} <- Actors.increase_event_count(actor) do
{:ok, activity}
{:ok, activity, object}
else
err ->
Logger.error("Something went wrong")
@@ -147,9 +155,9 @@ defmodule Mobilizon.Service.ActivityPub do
local = !(params[:local] == false)
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
{:ok, activity, _object} <- insert(data, local),
{:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
{:ok, activity, object}
end
end
@@ -164,9 +172,9 @@ defmodule Mobilizon.Service.ActivityPub do
"actor" => actor,
"object" => object
},
{:ok, activity, _object} <- insert(data, local),
{:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
{:ok, activity, object}
end
end
@@ -179,7 +187,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do
# with nil <- get_existing_like(url, object),
# like_data <- make_like_data(user, object, activity_id),
# {:ok, activity, _object} <- insert(like_data, local),
# {:ok, activity, object} <- insert(like_data, local),
# {:ok, object} <- add_like_to_object(activity, object),
# :ok <- maybe_federate(activity) do
# {:ok, activity, object}
@@ -215,7 +223,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do
# #with true <- is_public?(object),
# with announce_data <- make_announce_data(actor, object, activity_id),
# {:ok, activity, _object} <- insert(announce_data, local),
# {:ok, activity, object} <- insert(announce_data, local),
# # {:ok, object} <- add_announce_to_object(activity, object),
# :ok <- maybe_federate(activity) do
# {:ok, activity, object}
@@ -250,9 +258,9 @@ defmodule Mobilizon.Service.ActivityPub do
activity_follow_id <-
activity_id || "#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity",
data <- make_follow_data(followed, follower, activity_follow_id),
{:ok, activity, _object} <- insert(data, local),
{:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
{:ok, activity, object}
else
{err, _} when err in [:already_following, :suspended] ->
{:error, err}
@@ -269,9 +277,9 @@ defmodule Mobilizon.Service.ActivityPub do
data <- make_follow_data(followed, follower, follow_id),
{:ok, follow_activity, _object} <- insert(data, local),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
{:ok, activity, _object} <- insert(unfollow_data, local),
{:ok, activity, object} <- insert(unfollow_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
{:ok, activity, object}
else
err ->
Logger.error(inspect(err))
@@ -290,9 +298,9 @@ defmodule Mobilizon.Service.ActivityPub do
}
with {:ok, _} <- Events.delete_event(event),
{:ok, activity, _object} <- insert(data, local),
{:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
{:ok, activity, object}
end
end
@@ -305,9 +313,9 @@ defmodule Mobilizon.Service.ActivityPub do
}
with {:ok, _} <- Events.delete_comment(comment),
{:ok, activity, _object} <- insert(data, local),
{:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
{:ok, activity, object}
end
end
@@ -320,9 +328,9 @@ defmodule Mobilizon.Service.ActivityPub do
}
with {:ok, _} <- Actors.delete_actor(actor),
{:ok, activity, _object} <- insert(data, local),
{:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
{:ok, activity, object}
end
end

View File

@@ -0,0 +1,58 @@
defmodule Mobilizon.Service.ActivityPub.Converters.Address do
@moduledoc """
Flag converter
This module allows to convert reports from ActivityStream format to our own internal one, and back.
Note: Reports are named Flag in AS.
"""
alias Mobilizon.Addresses.Address, as: AddressModel
alias Mobilizon.Service.ActivityPub.Converter
@behaviour Converter
@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
res = %{
"description" => object["name"],
"url" => object["url"]
}
res =
if is_nil(object["address"]) do
res
else
Map.merge(res, %{
"country" => object["address"]["addressCountry"],
"postal_code" => object["address"]["postalCode"],
"region" => object["address"]["addressRegion"],
"street" => object["address"]["streetAddress"],
"locality" => object["address"]["addressLocality"]
})
end
if is_nil(object["geo"]) do
res
else
geo = %Geo.Point{
coordinates: {object["geo"]["latitude"], object["geo"]["longitude"]},
srid: 4326
}
Map.put(res, "geom", geo)
end
end
@doc """
Convert an event struct to an ActivityStream representation
"""
@impl Converter
@spec model_to_as(AddressModel.t()) :: map()
def model_to_as(%AddressModel{} = _address) do
nil
end
end

View File

@@ -12,19 +12,28 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
alias Mobilizon.Service.ActivityPub.Converter
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
with {:ok, %Actor{id: actor_id}} <- Actors.get_actor_by_url(object["actor"]),
tags <- fetch_tags(object["tag"]) do
Logger.debug("event as_to_model_data")
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"])} do
picture_id =
with true <- Map.has_key?(object, "attachment"),
with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0,
%Picture{id: picture_id} <-
Media.get_picture_by_url(
object["attachment"]
@@ -38,27 +47,64 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
_ -> nil
end
%{
"title" => object["name"],
"description" => object["content"],
"organizer_actor_id" => actor_id,
"picture_id" => picture_id,
"begins_on" => object["begins_on"],
"category" => object["category"],
"url" => object["id"],
"uuid" => object["uuid"],
"tags" => tags
}
{:ok,
%{
"title" => object["name"],
"description" => object["content"],
"organizer_actor_id" => actor_id,
"picture_id" => picture_id,
"begins_on" => object["startTime"],
"category" => object["category"],
"url" => object["id"],
"uuid" => object["uuid"],
"tags" => tags,
"physical_address_id" => address_id
}}
else
err ->
{:error, err}
end
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
Enum.reduce(tags, [], fn tag, acc ->
case Events.get_or_create_tag(tag) do
{:ok, %Tag{} = tag} ->
acc ++ [tag]
_ ->
with true <- tag["type"] == "Hashtag",
{:ok, %Tag{} = tag} <- Events.get_or_create_tag(tag) do
acc ++ [tag]
else
_err ->
acc
end
end)

View File

@@ -132,9 +132,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
Logger.info("Handle incoming to create notes")
@@ -159,15 +156,39 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object} = data) do
Logger.info("Handle incoming to create event")
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(data["actor"]) do
Logger.debug("found actor")
Logger.debug(inspect(actor))
params = %{
to: data["to"],
object: object |> fix_object,
actor: actor,
local: false,
published: data["published"],
additional:
Map.take(data, [
"cc",
"id"
])
}
ActivityPub.create(params)
end
end
def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
) do
with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true),
{:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
{:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do
ActivityPub.accept(%{to: [follower.url], actor: followed.url, object: data, local: true})
{:ok, activity}
{:ok, activity, object}
else
e ->
Logger.error("Unable to handle Follow activity")
@@ -257,9 +278,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
) do
with {:ok, %Actor{domain: nil} = followed} <- Actors.get_actor_by_url(followed),
{:ok, %Actor{} = follower} <- Actors.get_actor_by_url(follower),
{:ok, activity} <- ActivityPub.unfollow(followed, follower, id, false) do
{:ok, activity, object} <- ActivityPub.unfollow(followed, follower, id, false) do
Actor.unfollow(follower, followed)
{:ok, activity}
{:ok, activity, object}
else
e ->
Logger.error(inspect(e))
@@ -282,11 +303,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
# TODO : Validate that DELETE comes indeed form right domain (see above)
# :ok <- contain_origin(actor_url, object.data),
{:ok, activity} <- ActivityPub.delete(object, false) do
{:ok, activity}
{:ok, activity, object} <- ActivityPub.delete(object, false) do
{:ok, activity, object}
else
e ->
Logger.debug(inspect(e))
Logger.error(inspect(e))
:error
end
end

View File

@@ -11,6 +11,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"""
alias Mobilizon.Repo
alias Mobilizon.Addresses.Address
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Event
@@ -122,7 +123,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"""
def insert_full_object(%{"object" => %{"type" => "Event"} = object_data})
when is_map(object_data) do
with object_data <-
with {:ok, object_data} <-
Converters.Event.as_to_model_data(object_data),
{:ok, %Event{} = event} <- Events.create_event(object_data) do
{:ok, event}
@@ -260,26 +261,21 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"""
@spec make_event_data(
String.t(),
String.t(),
map(),
String.t(),
String.t(),
map(),
list(),
list(),
map(),
String.t()
map()
) :: map()
def make_event_data(
actor,
to,
%{to: to, cc: cc} = _audience,
title,
content_html,
picture \\ nil,
tags \\ [],
# _cw \\ nil,
cc \\ [],
metadata \\ %{},
category \\ ""
metadata \\ %{}
) do
Logger.debug("Making event data")
uuid = Ecto.UUID.generate()
@@ -287,21 +283,58 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
res = %{
"type" => "Event",
"to" => to,
"cc" => cc,
"cc" => cc || [],
"content" => content_html,
"name" => title,
# "summary" => cw,
"begins_on" => metadata.begins_on,
"category" => category,
"startTime" => metadata.begins_on,
"category" => metadata.category,
"actor" => actor,
"id" => Routes.page_url(Endpoint, :event, uuid),
"uuid" => uuid,
"tag" => tags |> Enum.uniq()
"tag" =>
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
}
res =
if is_nil(metadata.physical_address),
do: res,
else: Map.put(res, "location", make_address_data(metadata.physical_address))
if is_nil(picture), do: res, else: Map.put(res, "attachment", [make_picture_data(picture)])
end
def make_address_data(%Address{} = address) do
res = %{
"type" => "Place",
"name" => address.description,
"id" => address.url,
"address" => %{
"type" => "PostalAddress",
"streetAddress" => address.street,
"postalCode" => address.postal_code,
"addressLocality" => address.locality,
"addressRegion" => address.region,
"addressCountry" => address.country
}
}
if is_nil(address.geom) do
res
else
Map.put(res, "geo", %{
"type" => "GeoCoordinates",
"latitude" => address.geom.coordinates |> elem(0),
"longitude" => address.geom.coordinates |> elem(1)
})
end
end
def make_address_data(address) do
Address
|> struct(address)
|> make_address_data()
end
@doc """
Make an AP comment object from an set of values
"""

View File

@@ -52,7 +52,7 @@ defmodule Mobilizon.Service.Federator do
Logger.debug(inspect(params))
case Transmogrifier.handle_incoming(params) do
{:ok, activity} ->
{:ok, activity, _} ->
{:ok, activity}
%Activity{} ->