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:
Thomas Citharel
2019-10-25 17:43:37 +02:00
parent 814cfbc8eb
commit cc820d6b63
69 changed files with 1881 additions and 1424 deletions

View File

@@ -2,55 +2,18 @@ defmodule MobilizonWeb.API.Comments do
@moduledoc """
API for Comments.
"""
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Comment
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
alias MobilizonWeb.API.Utils
alias Mobilizon.Service.ActivityPub.Activity
@doc """
Create a comment
Creates a comment from an actor and a status
"""
@spec create_comment(String.t(), String.t(), String.t()) ::
@spec create_comment(map()) ::
{:ok, Activity.t(), Comment.t()} | any()
def create_comment(
from_username,
status,
visibility \\ :public,
in_reply_to_comment_URL \\ nil
) do
with {:local_actor, %Actor{url: url} = actor} <-
{:local_actor, Actors.get_local_actor_by_name(from_username)},
in_reply_to_comment <- get_in_reply_to_comment(in_reply_to_comment_URL),
{content_html, tags, to, cc} <-
Utils.prepare_content(actor, status, visibility, [], in_reply_to_comment),
comment <-
ActivityPubUtils.make_comment_data(
url,
to,
content_html,
in_reply_to_comment,
tags,
cc
) do
ActivityPub.create(%{
to: to,
actor: actor,
object: comment,
local: true
})
end
end
@spec get_in_reply_to_comment(nil) :: nil
defp get_in_reply_to_comment(nil), do: nil
@spec get_in_reply_to_comment(String.t()) :: Comment.t()
defp get_in_reply_to_comment(in_reply_to_comment_url) do
ActivityPub.fetch_object_from_url(in_reply_to_comment_url)
def create_comment(args) do
ActivityPub.create(:comment, args, true)
end
end

View File

@@ -3,37 +3,24 @@ defmodule MobilizonWeb.API.Events do
API for Events.
"""
alias Mobilizon.Events.Event
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Event
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.Activity
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
alias MobilizonWeb.API.Utils
alias Mobilizon.Service.ActivityPub.Utils
@doc """
Create an event
"""
@spec create_event(map()) :: {:ok, Activity.t(), Event.t()} | any()
def create_event(%{organizer_actor: organizer_actor} = args) do
with args <- prepare_args(args),
event <-
ActivityPubUtils.make_event_data(
args.organizer_actor.url,
%{to: args.to, cc: args.cc},
args.title,
args.content_html,
args.picture,
args.tags,
args.metadata
) do
ActivityPub.create(%{
to: args.to,
actor: organizer_actor,
object: event,
# For now we don't federate drafts but it will be needed if we want to edit them as groups
local: args.metadata.draft == false
})
def create_event(args) do
with organizer_actor <- Map.get(args, :organizer_actor),
args <-
Map.update(args, :picture, nil, fn picture ->
process_picture(picture, organizer_actor)
end) do
# For now we don't federate drafts but it will be needed if we want to edit them as groups
ActivityPub.create(:event, args, args.draft == false)
end
end
@@ -41,65 +28,13 @@ defmodule MobilizonWeb.API.Events do
Update an event
"""
@spec update_event(map(), Event.t()) :: {:ok, Activity.t(), Event.t()} | any()
def update_event(
%{
organizer_actor: organizer_actor
} = args,
%Event{} = event
) do
with args <- Map.put(args, :tags, Map.get(args, :tags, [])),
args <- prepare_args(Map.merge(event, args)),
event <-
ActivityPubUtils.make_event_data(
args.organizer_actor.url,
%{to: args.to, cc: args.cc},
args.title,
args.content_html,
args.picture,
args.tags,
args.metadata,
event.uuid,
event.url
) do
ActivityPub.update(%{
to: args.to,
actor: organizer_actor.url,
cc: [],
object: event,
local: args.metadata.draft == false
})
end
end
defp prepare_args(args) do
with %Actor{} = organizer_actor <- Map.get(args, :organizer_actor),
title <- args |> Map.get(:title, "") |> HtmlSanitizeEx.strip_tags() |> String.trim(),
visibility <- Map.get(args, :visibility, :public),
description <- Map.get(args, :description),
tags <- Map.get(args, :tags),
{content_html, tags, to, cc} <-
Utils.prepare_content(organizer_actor, description, visibility, tags, nil) do
%{
title: title,
content_html: content_html,
picture: Map.get(args, :picture),
tags: tags,
organizer_actor: organizer_actor,
to: to,
cc: cc,
metadata: %{
begins_on: Map.get(args, :begins_on),
ends_on: Map.get(args, :ends_on),
physical_address: Map.get(args, :physical_address),
category: Map.get(args, :category),
options: Map.get(args, :options),
join_options: Map.get(args, :join_options),
status: Map.get(args, :status),
online_address: Map.get(args, :online_address),
phone_address: Map.get(args, :phone_address),
draft: Map.get(args, :draft)
}
}
def update_event(args, %Event{} = event) do
with organizer_actor <- Map.get(args, :organizer_actor),
args <-
Map.update(args, :picture, nil, fn picture ->
process_picture(picture, organizer_actor)
end) do
ActivityPub.update(:event, event, args, Map.get(args, :draft, false) == false)
end
end
@@ -111,4 +46,15 @@ defmodule MobilizonWeb.API.Events do
def delete_event(%Event{} = event, federate \\ true) do
ActivityPub.delete(event, federate)
end
defp process_picture(nil, _), do: nil
defp process_picture(%{picture_id: _picture_id} = args, _), do: args
defp process_picture(%{picture: picture}, %Actor{id: actor_id}) do
%{
file:
picture |> Map.get(:file) |> Utils.make_picture_data(description: Map.get(picture, :name)),
actor_id: actor_id
}
end
end

View File

@@ -6,6 +6,7 @@ defmodule MobilizonWeb.API.Follows do
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, Follower}
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.Activity
require Logger
@@ -32,17 +33,14 @@ defmodule MobilizonWeb.API.Follows do
end
def accept(%Actor{} = follower, %Actor{} = followed) do
with %Follower{approved: false, id: follow_id, url: follow_url} = follow <-
with %Follower{approved: false} = follow <-
Actors.is_following(follower, followed),
activity_follow_url <- "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}",
data <-
ActivityPub.Utils.make_follow_data(followed, follower, follow_url),
{:ok, activity, _} <-
{:ok, %Activity{} = activity, %Follower{approved: true}} <-
ActivityPub.accept(
%{to: [follower.url], actor: followed.url, object: data},
activity_follow_url
),
{:ok, %Follower{approved: true}} <- Actors.update_follower(follow, %{"approved" => true}) do
:follow,
follow,
%{approved: true}
) do
{:ok, activity}
else
%Follower{approved: true} ->

View File

@@ -6,38 +6,19 @@ defmodule MobilizonWeb.API.Groups do
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
alias Mobilizon.Users.User
alias MobilizonWeb.API.Utils
alias Mobilizon.Service.ActivityPub.Activity
@doc """
Create a group
"""
@spec create_group(User.t(), map()) :: {:ok, Activity.t(), Group.t()} | any()
def create_group(
user,
%{
preferred_username: title,
summary: summary,
creator_actor_id: creator_actor_id,
avatar: _avatar,
banner: _banner
} = args
) do
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, creator_actor_id),
title <- String.trim(title),
{:existing_group, nil} <- {:existing_group, Actors.get_group_by_title(title)},
visibility <- Map.get(args, :visibility, :public),
{content_html, tags, to, cc} <-
Utils.prepare_content(actor, summary, visibility, [], nil),
group <- ActivityPubUtils.make_group_data(actor.url, to, title, content_html, tags, cc) do
ActivityPub.create(%{
to: ["https://www.w3.org/ns/activitystreams#Public"],
actor: actor,
object: group,
local: true
})
@spec create_group(map()) :: {:ok, Activity.t(), Actor.t()} | any()
def create_group(args) do
with preferred_username <-
args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(),
{:existing_group, nil} <-
{:existing_group, Actors.get_local_group_by_title(preferred_username)},
{:ok, %Activity{} = activity, %Actor{} = group} <- ActivityPub.create(:group, args, true) do
{:ok, activity, group}
else
{:existing_group, _} ->
{:error, "A group with this name already exists"}

View File

@@ -38,12 +38,11 @@ defmodule MobilizonWeb.API.Participations do
) do
with {:ok, activity, _} <-
ActivityPub.accept(
%{
to: [participation.actor.url],
actor: moderator.url,
object: participation.url
},
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{participation.id}"
:join,
participation,
%{role: :participant},
true,
%{"to" => [moderator.url]}
),
{:ok, %Participant{role: :participant} = participation} <-
Events.update_participant(participation, %{"role" => :participant}),

View File

@@ -3,89 +3,9 @@ defmodule MobilizonWeb.API.Utils do
Utils for API.
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Config
alias Mobilizon.Service.Formatter
@ap_public "https://www.w3.org/ns/activitystreams#Public"
@doc """
Determines the full audience based on mentions for a public audience
Audience is:
* `to` : the mentioned actors, the eventual actor we're replying to and the public
* `cc` : the actor's followers
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :public) do
to = [@ap_public | mentions]
cc = [actor.followers_url]
if inReplyTo do
{Enum.uniq([inReplyTo.actor | to]), cc}
else
{to, cc}
end
end
@doc """
Determines the full audience based on mentions based on a unlisted audience
Audience is:
* `to` : the mentionned actors, actor's followers and the eventual actor we're replying to
* `cc` : public
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :unlisted) do
to = [actor.followers_url | mentions]
cc = [@ap_public]
if inReplyTo do
{Enum.uniq([inReplyTo.actor | to]), cc}
else
{to, cc}
end
end
@doc """
Determines the full audience based on mentions based on a private audience
Audience is:
* `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
* `cc` : none
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :private) do
{to, cc} = get_to_and_cc(actor, mentions, inReplyTo, :direct)
{[actor.followers_url | to], cc}
end
@doc """
Determines the full audience based on mentions based on a direct audience
Audience is:
* `to` : the mentioned actors and the eventual actor we're replying to
* `cc` : none
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(_actor, mentions, inReplyTo, :direct) do
if inReplyTo do
{Enum.uniq([inReplyTo.actor | mentions]), []}
else
{mentions, []}
end
end
def get_to_and_cc(_actor, mentions, _inReplyTo, {:list, _}) do
{mentions, []}
end
# def get_addressed_users(_, to) when is_list(to) do
# Actors.get(to)
# end
def get_addressed_users(mentioned_users, _), do: mentioned_users
@doc """
Creates HTML content from text and mentions
"""
@@ -126,19 +46,4 @@ defmodule MobilizonWeb.API.Utils do
{:error, "Comment must be up to #{max_size} characters"}
end
end
def prepare_content(actor, content, visibility, tags, in_reply_to) do
with content <- String.trim(content || ""),
{content_html, mentions, tags} <-
make_content_html(
content,
tags,
"text/html"
),
mentioned_users <- for({_, mentioned_user} <- mentions, do: mentioned_user.url),
addressed_users <- get_addressed_users(mentioned_users, nil),
{to, cc} <- get_to_and_cc(actor, addressed_users, in_reply_to, visibility) do
{content_html, tags, to, cc}
end
end
end