Refactor Core things, including Ecto handling, ActivityPub & Transmogrifier modules
* Data doesn't need anymore to be converted to ActivityStream format to be saved (this was taken from Pleroma and not at all a good idea here) * Everything saved when creating an event is inserted into PostgreSQL in a single transaction
This commit is contained in:
@@ -37,17 +37,18 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do
|
||||
"url" => object["image"]["url"]
|
||||
}
|
||||
|
||||
%{
|
||||
"type" => String.to_existing_atom(object["type"]),
|
||||
"preferred_username" => object["preferredUsername"],
|
||||
"summary" => object["summary"],
|
||||
"url" => object["id"],
|
||||
"name" => object["name"],
|
||||
"avatar" => avatar,
|
||||
"banner" => banner,
|
||||
"keys" => object["publicKey"]["publicKeyPem"],
|
||||
"manually_approves_followers" => object["manuallyApprovesFollowers"]
|
||||
}
|
||||
{:ok,
|
||||
%{
|
||||
"type" => String.to_existing_atom(object["type"]),
|
||||
"preferred_username" => object["preferredUsername"],
|
||||
"summary" => object["summary"],
|
||||
"url" => object["id"],
|
||||
"name" => object["name"],
|
||||
"avatar" => avatar,
|
||||
"banner" => banner,
|
||||
"keys" => object["publicKey"]["publicKeyPem"],
|
||||
"manually_approves_followers" => object["manuallyApprovesFollowers"]
|
||||
}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -11,6 +11,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
|
||||
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
|
||||
|
||||
require Logger
|
||||
|
||||
@@ -26,57 +27,67 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||
Converts an AP object data to our internal data structure.
|
||||
"""
|
||||
@impl Converter
|
||||
@spec as_to_model_data(map) :: map
|
||||
@spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
|
||||
def as_to_model_data(object) do
|
||||
{:ok, %Actor{id: actor_id}} = ActivityPub.get_or_fetch_by_url(object["actor"])
|
||||
|
||||
Logger.debug("Inserting full comment")
|
||||
Logger.debug("We're converting raw ActivityStream data to a comment entity")
|
||||
Logger.debug(inspect(object))
|
||||
|
||||
data = %{
|
||||
"text" => object["content"],
|
||||
"url" => object["id"],
|
||||
"actor_id" => actor_id,
|
||||
"in_reply_to_comment_id" => nil,
|
||||
"event_id" => nil,
|
||||
"uuid" => object["uuid"]
|
||||
}
|
||||
with {:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(object["actor"]),
|
||||
{:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
|
||||
{:mentions, mentions} <- {:mentions, ConverterUtils.fetch_mentions(object["tag"])} do
|
||||
Logger.debug("Inserting full comment")
|
||||
Logger.debug(inspect(object))
|
||||
|
||||
# We fetch the parent object
|
||||
Logger.debug("We're fetching the parent object")
|
||||
data = %{
|
||||
text: object["content"],
|
||||
url: object["id"],
|
||||
actor_id: actor_id,
|
||||
in_reply_to_comment_id: nil,
|
||||
event_id: nil,
|
||||
uuid: object["uuid"],
|
||||
tags: tags,
|
||||
mentions: mentions
|
||||
}
|
||||
|
||||
data =
|
||||
if Map.has_key?(object, "inReplyTo") && object["inReplyTo"] != nil &&
|
||||
object["inReplyTo"] != "" do
|
||||
Logger.debug(fn -> "Object has inReplyTo #{object["inReplyTo"]}" end)
|
||||
# We fetch the parent object
|
||||
Logger.debug("We're fetching the parent object")
|
||||
|
||||
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
|
||||
# Reply to an event (Event)
|
||||
{:ok, %Event{id: id}} ->
|
||||
Logger.debug("Parent object is an event")
|
||||
data |> Map.put("event_id", id)
|
||||
data =
|
||||
if Map.has_key?(object, "inReplyTo") && object["inReplyTo"] != nil &&
|
||||
object["inReplyTo"] != "" do
|
||||
Logger.debug(fn -> "Object has inReplyTo #{object["inReplyTo"]}" end)
|
||||
|
||||
# Reply to a comment (Comment)
|
||||
{:ok, %CommentModel{id: id} = comment} ->
|
||||
Logger.debug("Parent object is another comment")
|
||||
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
|
||||
# Reply to an event (Event)
|
||||
{:ok, %Event{id: id}} ->
|
||||
Logger.debug("Parent object is an event")
|
||||
data |> Map.put(:event_id, id)
|
||||
|
||||
data
|
||||
|> Map.put("in_reply_to_comment_id", id)
|
||||
|> Map.put("origin_comment_id", comment |> CommentModel.get_thread_id())
|
||||
# Reply to a comment (Comment)
|
||||
{:ok, %CommentModel{id: id} = comment} ->
|
||||
Logger.debug("Parent object is another comment")
|
||||
|
||||
# Anything else is kind of a MP
|
||||
{:error, parent} ->
|
||||
Logger.debug("Parent object is something we don't handle")
|
||||
Logger.debug(inspect(parent))
|
||||
data
|
||||
data
|
||||
|> Map.put(:in_reply_to_comment_id, id)
|
||||
|> Map.put(:origin_comment_id, comment |> CommentModel.get_thread_id())
|
||||
|
||||
# Anything else is kind of a MP
|
||||
{:error, parent} ->
|
||||
Logger.debug("Parent object is something we don't handle")
|
||||
Logger.debug(inspect(parent))
|
||||
data
|
||||
end
|
||||
else
|
||||
Logger.debug("No parent object for this comment")
|
||||
|
||||
data
|
||||
end
|
||||
else
|
||||
Logger.debug("No parent object for this comment")
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
data
|
||||
{:ok, data}
|
||||
else
|
||||
err ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -85,14 +96,22 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||
@impl Converter
|
||||
@spec model_to_as(CommentModel.t()) :: map
|
||||
def model_to_as(%CommentModel{} = comment) do
|
||||
to =
|
||||
if comment.visibility == :public,
|
||||
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
else: [comment.actor.followers_url]
|
||||
|
||||
object = %{
|
||||
"type" => "Note",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"to" => to,
|
||||
"cc" => [],
|
||||
"content" => comment.text,
|
||||
"actor" => comment.actor.url,
|
||||
"attributedTo" => comment.actor.url,
|
||||
"uuid" => comment.uuid,
|
||||
"id" => comment.url
|
||||
"id" => comment.url,
|
||||
"tag" =>
|
||||
ConverterUtils.build_mentions(comment.mentions) ++ ConverterUtils.build_tags(comment.tags)
|
||||
}
|
||||
|
||||
if comment.in_reply_to_comment do
|
||||
|
||||
@@ -6,15 +6,17 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||
internal one, and back.
|
||||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Addresses, Events, Media}
|
||||
alias Mobilizon.{Addresses, Media}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Addresses.Address
|
||||
alias Mobilizon.Events.Event, as: EventModel
|
||||
alias Mobilizon.Events.{EventOptions, Tag}
|
||||
alias Mobilizon.Events.EventOptions
|
||||
alias Mobilizon.Media.Picture
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils}
|
||||
alias Mobilizon.Service.ActivityPub.Converter.Address, as: AddressConverter
|
||||
alias Mobilizon.Service.ActivityPub.Converter.Picture, as: PictureConverter
|
||||
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
|
||||
|
||||
require Logger
|
||||
|
||||
@@ -30,16 +32,16 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||
Converts an AP object data to our internal data structure.
|
||||
"""
|
||||
@impl Converter
|
||||
@spec as_to_model_data(map) :: map
|
||||
@spec as_to_model_data(map) :: {:ok, map()} | {:error, any()}
|
||||
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"])},
|
||||
{:actor, ActivityPub.get_or_fetch_actor_by_url(object["actor"])},
|
||||
{:address, address_id} <-
|
||||
{:address, get_address(object["location"])},
|
||||
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
|
||||
{:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
|
||||
{:visibility, visibility} <- {:visibility, get_visibility(object)},
|
||||
{:options, options} <- {:options, get_options(object)} do
|
||||
picture_id =
|
||||
@@ -58,26 +60,27 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||
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"],
|
||||
"visibility" => visibility,
|
||||
"join_options" => object["joinOptions"],
|
||||
"status" => object["status"],
|
||||
"online_address" => object["onlineAddress"],
|
||||
"phone_address" => object["phoneAddress"],
|
||||
"draft" => object["draft"] || false,
|
||||
"url" => object["id"],
|
||||
"uuid" => object["uuid"],
|
||||
"tags" => tags,
|
||||
"physical_address_id" => address_id
|
||||
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"],
|
||||
visibility: visibility,
|
||||
join_options: Map.get(object, "joinOptions", "free"),
|
||||
options: options,
|
||||
status: object["status"],
|
||||
online_address: object["onlineAddress"],
|
||||
phone_address: object["phoneAddress"],
|
||||
draft: object["draft"] || false,
|
||||
url: object["id"],
|
||||
uuid: object["uuid"],
|
||||
tags: tags,
|
||||
physical_address_id: address_id
|
||||
}
|
||||
|
||||
{:ok, Map.put(entity, "options", options)}
|
||||
{:ok, entity}
|
||||
else
|
||||
error ->
|
||||
{:error, error}
|
||||
@@ -111,7 +114,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||
"startTime" => event.begins_on |> date_to_string(),
|
||||
"joinOptions" => to_string(event.join_options),
|
||||
"endTime" => event.ends_on |> date_to_string(),
|
||||
"tag" => event.tags |> build_tags(),
|
||||
"tag" => event.tags |> ConverterUtils.build_tags(),
|
||||
"draft" => event.draft,
|
||||
"id" => event.url,
|
||||
"url" => event.url
|
||||
@@ -181,32 +184,6 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||
end
|
||||
end
|
||||
|
||||
@spec fetch_tags([String.t()]) :: [String.t()]
|
||||
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
|
||||
|
||||
@spec build_tags([String.t()]) :: String.t()
|
||||
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
|
||||
|
||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
defp get_visibility(object) do
|
||||
|
||||
36
lib/service/activity_pub/converter/follower.ex
Normal file
36
lib/service/activity_pub/converter/follower.ex
Normal file
@@ -0,0 +1,36 @@
|
||||
defmodule Mobilizon.Service.ActivityPub.Converter.Follower do
|
||||
@moduledoc """
|
||||
Participant converter.
|
||||
|
||||
This module allows to convert followers from ActivityStream format to our own
|
||||
internal one, and back.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.Follower, as: FollowerModel
|
||||
|
||||
alias Mobilizon.Service.ActivityPub.Convertible
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
defimpl Convertible, for: FollowerModel do
|
||||
alias Mobilizon.Service.ActivityPub.Converter.Follower, as: FollowerConverter
|
||||
|
||||
defdelegate model_to_as(follower), to: FollowerConverter
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert an follow struct to an ActivityStream representation.
|
||||
"""
|
||||
@spec model_to_as(FollowerModel.t()) :: map
|
||||
def model_to_as(
|
||||
%FollowerModel{actor: %Actor{} = actor, target_actor: %Actor{} = target_actor} = follower
|
||||
) do
|
||||
%{
|
||||
"type" => "Follow",
|
||||
"actor" => actor.url,
|
||||
"to" => [target_actor.url],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object" => target_actor.url,
|
||||
"id" => follower.url
|
||||
}
|
||||
end
|
||||
end
|
||||
100
lib/service/activity_pub/converter/utils.ex
Normal file
100
lib/service/activity_pub/converter/utils.ex
Normal file
@@ -0,0 +1,100 @@
|
||||
defmodule Mobilizon.Service.ActivityPub.Converter.Utils do
|
||||
@moduledoc """
|
||||
Various utils for converters
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.Tag
|
||||
alias Mobilizon.Mention
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
alias Mobilizon.Storage.Repo
|
||||
require Logger
|
||||
|
||||
@spec fetch_tags([String.t()]) :: [Tag.t()]
|
||||
def fetch_tags(tags) when is_list(tags) do
|
||||
Logger.debug("fetching tags")
|
||||
|
||||
Enum.reduce(tags, [], &fetch_tag/2)
|
||||
end
|
||||
|
||||
@spec fetch_mentions([map()]) :: [map()]
|
||||
def fetch_mentions(mentions) when is_list(mentions) do
|
||||
Logger.debug("fetching mentions")
|
||||
|
||||
Enum.reduce(mentions, [], fn mention, acc -> create_mention(mention, acc) end)
|
||||
end
|
||||
|
||||
def fetch_address(%{id: id}) do
|
||||
with {id, ""} <- Integer.parse(id) do
|
||||
%{id: id}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_address(address) when is_map(address) do
|
||||
address
|
||||
end
|
||||
|
||||
@spec build_tags([Tag.t()]) :: [Map.t()]
|
||||
def build_tags(tags) do
|
||||
Enum.map(tags, fn %Tag{} = tag ->
|
||||
%{
|
||||
"href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag.slug}",
|
||||
"name" => "##{tag.title}",
|
||||
"type" => "Hashtag"
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
def build_mentions(mentions) do
|
||||
Enum.map(mentions, fn %Mention{} = mention ->
|
||||
if Ecto.assoc_loaded?(mention.actor) do
|
||||
build_mention(mention.actor)
|
||||
else
|
||||
build_mention(Repo.preload(mention, [:actor]).actor)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp build_mention(%Actor{} = actor) do
|
||||
%{
|
||||
"href" => actor.url,
|
||||
"name" => "@#{Mobilizon.Actors.Actor.preferred_username_and_domain(actor)}",
|
||||
"type" => "Mention"
|
||||
}
|
||||
end
|
||||
|
||||
defp fetch_tag(tag, acc) when is_map(tag) do
|
||||
case tag["type"] do
|
||||
"Hashtag" ->
|
||||
acc ++ [%{title: tag}]
|
||||
|
||||
_err ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_tag(tag, acc) when is_bitstring(tag) do
|
||||
acc ++ [%{title: tag}]
|
||||
end
|
||||
|
||||
@spec create_mention(map(), list()) :: list()
|
||||
defp create_mention(%Actor{id: actor_id} = _mention, acc) do
|
||||
acc ++ [%{actor_id: actor_id}]
|
||||
end
|
||||
|
||||
@spec create_mention(map(), list()) :: list()
|
||||
defp create_mention(mention, acc) when is_map(mention) do
|
||||
with true <- mention["type"] == "Mention",
|
||||
{:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(mention["href"]) do
|
||||
acc ++ [%{actor_id: actor_id}]
|
||||
else
|
||||
_err ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
@spec create_mention({String.t(), map()}, list()) :: list()
|
||||
defp create_mention({_, mention}, acc) when is_map(mention) do
|
||||
create_mention(mention, acc)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user