Refactor Mobilizon.Federation.ActivityPub and add typespecs
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
41f086e2c9
commit
b5d9b82bdd
@ -1,12 +1,12 @@
|
||||
%Doctor.Config{
|
||||
exception_moduledoc_required: true,
|
||||
failed: false,
|
||||
ignore_modules: [],
|
||||
ignore_modules: [Mobilizon.Web, Mobilizon.GraphQL.Schema, Mobilizon.Service.Activity.Renderer, Mobilizon.Service.Workers.Helper],
|
||||
ignore_paths: [],
|
||||
min_module_doc_coverage: 70,
|
||||
min_module_doc_coverage: 100,
|
||||
min_module_spec_coverage: 50,
|
||||
min_overall_doc_coverage: 90,
|
||||
min_overall_spec_coverage: 30,
|
||||
min_overall_doc_coverage: 100,
|
||||
min_overall_spec_coverage: 90,
|
||||
moduledoc_required: true,
|
||||
raise: false,
|
||||
reporter: Doctor.Reporters.Full,
|
||||
|
@ -4,8 +4,10 @@ defmodule Mobilizon.ConfigProvider do
|
||||
"""
|
||||
@behaviour Config.Provider
|
||||
|
||||
@spec init(String.t()) :: String.t()
|
||||
def init(path) when is_binary(path), do: path
|
||||
|
||||
@spec load(Keyword.t(), String.t()) :: Keyword.t()
|
||||
def load(config, path) do
|
||||
config_path = System.get_env("MOBILIZON_CONFIG_PATH") || path
|
||||
|
||||
|
169
lib/federation/activity_pub/actions/accept.ex
Normal file
169
lib/federation/activity_pub/actions/accept.ex
Normal file
@ -0,0 +1,169 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do
|
||||
@moduledoc """
|
||||
Accept things
|
||||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Events}
|
||||
alias Mobilizon.Actors.{Actor, Follower, Member}
|
||||
alias Mobilizon.Events.Participant
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Refresher}
|
||||
alias Mobilizon.Federation.ActivityStream
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Service.Notifications.Scheduler
|
||||
alias Mobilizon.Web.Endpoint
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
make_accept_join_data: 2,
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@type acceptable_types :: :join | :follow | :invite
|
||||
@type acceptable_entities ::
|
||||
accept_join_entities | accept_follow_entities | accept_invite_entities
|
||||
|
||||
@spec accept(acceptable_types, acceptable_entities, boolean, map) ::
|
||||
{:ok, ActivityStream.t(), acceptable_entities}
|
||||
def accept(type, entity, local \\ true, additional \\ %{}) do
|
||||
Logger.debug("We're accepting something")
|
||||
|
||||
accept_res =
|
||||
case type do
|
||||
:join -> accept_join(entity, additional)
|
||||
:follow -> accept_follow(entity, additional)
|
||||
:invite -> accept_invite(entity, additional)
|
||||
end
|
||||
|
||||
with {:ok, entity, update_data} <- accept_res do
|
||||
{:ok, activity} = create_activity(update_data, local)
|
||||
maybe_federate(activity)
|
||||
maybe_relay_if_group_activity(activity)
|
||||
{:ok, activity, entity}
|
||||
end
|
||||
end
|
||||
|
||||
@type accept_follow_entities :: Follower.t()
|
||||
|
||||
@spec accept_follow(Follower.t(), map) ::
|
||||
{:ok, Follower.t(), Activity.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp accept_follow(%Follower{} = follower, additional) do
|
||||
with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, %{approved: true}) do
|
||||
follower_as_data = Convertible.model_to_as(follower)
|
||||
|
||||
update_data =
|
||||
make_accept_join_data(
|
||||
follower_as_data,
|
||||
Map.merge(additional, %{
|
||||
"id" => "#{Endpoint.url()}/accept/follow/#{follower.id}",
|
||||
"to" => [follower.actor.url],
|
||||
"cc" => [],
|
||||
"actor" => follower.target_actor.url
|
||||
})
|
||||
)
|
||||
|
||||
{:ok, follower, update_data}
|
||||
end
|
||||
end
|
||||
|
||||
@type accept_join_entities :: Participant.t() | Member.t()
|
||||
|
||||
@spec accept_join(Participant.t() | Member.t(), map) ::
|
||||
{:ok, Participant.t() | Member.t(), Activity.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp accept_join(%Participant{} = participant, additional) do
|
||||
with {:ok, %Participant{} = participant} <-
|
||||
Events.update_participant(participant, %{role: :participant}) do
|
||||
Absinthe.Subscription.publish(Endpoint, participant.actor,
|
||||
event_person_participation_changed: participant.actor.id
|
||||
)
|
||||
|
||||
Scheduler.trigger_notifications_for_participant(participant)
|
||||
participant_as_data = Convertible.model_to_as(participant)
|
||||
audience = Audience.get_audience(participant)
|
||||
|
||||
accept_join_data =
|
||||
make_accept_join_data(
|
||||
participant_as_data,
|
||||
Map.merge(Map.merge(audience, additional), %{
|
||||
"id" => "#{Endpoint.url()}/accept/join/#{participant.id}"
|
||||
})
|
||||
)
|
||||
|
||||
{:ok, participant, accept_join_data}
|
||||
end
|
||||
end
|
||||
|
||||
defp accept_join(%Member{} = member, additional) do
|
||||
with {:ok, %Member{} = member} <-
|
||||
Actors.update_member(member, %{role: :member}) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
subject: "member_approved"
|
||||
)
|
||||
|
||||
maybe_refresh_group(member)
|
||||
|
||||
Absinthe.Subscription.publish(Endpoint, member.actor,
|
||||
group_membership_changed: [
|
||||
Actor.preferred_username_and_domain(member.parent),
|
||||
member.actor.id
|
||||
]
|
||||
)
|
||||
|
||||
member_as_data = Convertible.model_to_as(member)
|
||||
audience = Audience.get_audience(member)
|
||||
|
||||
accept_join_data =
|
||||
make_accept_join_data(
|
||||
member_as_data,
|
||||
Map.merge(Map.merge(audience, additional), %{
|
||||
"id" => "#{Endpoint.url()}/accept/join/#{member.id}"
|
||||
})
|
||||
)
|
||||
|
||||
{:ok, member, accept_join_data}
|
||||
end
|
||||
end
|
||||
|
||||
@type accept_invite_entities :: Member.t()
|
||||
|
||||
@spec accept_invite(Member.t(), map()) ::
|
||||
{:ok, Member.t(), Activity.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp accept_invite(
|
||||
%Member{invited_by_id: invited_by_id, actor_id: actor_id} = member,
|
||||
_additional
|
||||
) do
|
||||
with %Actor{} = inviter <- Actors.get_actor!(invited_by_id),
|
||||
%Actor{url: actor_url} <- Actors.get_actor!(actor_id),
|
||||
{:ok, %Member{id: member_id} = member} <-
|
||||
Actors.update_member(member, %{role: :member}) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
subject: "member_accepted_invitation"
|
||||
)
|
||||
|
||||
maybe_refresh_group(member)
|
||||
|
||||
accept_data = %{
|
||||
"type" => "Accept",
|
||||
"attributedTo" => member.parent.url,
|
||||
"to" => [inviter.url, member.parent.members_url],
|
||||
"cc" => [member.parent.url],
|
||||
"actor" => actor_url,
|
||||
"object" => Convertible.model_to_as(member),
|
||||
"id" => "#{Endpoint.url()}/accept/invite/member/#{member_id}"
|
||||
}
|
||||
|
||||
{:ok, member, accept_data}
|
||||
end
|
||||
end
|
||||
|
||||
@spec maybe_refresh_group(Member.t()) :: :ok | nil
|
||||
defp maybe_refresh_group(%Member{
|
||||
parent: %Actor{domain: parent_domain, url: parent_url},
|
||||
actor: %Actor{} = actor
|
||||
}) do
|
||||
unless is_nil(parent_domain),
|
||||
do: Refresher.fetch_group(parent_url, actor)
|
||||
end
|
||||
end
|
60
lib/federation/activity_pub/actions/announce.ex
Normal file
60
lib/federation/activity_pub/actions/announce.ex
Normal file
@ -0,0 +1,60 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Announce do
|
||||
@moduledoc """
|
||||
Announce things
|
||||
"""
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.ActivityStream
|
||||
alias Mobilizon.Share
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
make_announce_data: 3,
|
||||
make_announce_data: 4,
|
||||
make_unannounce_data: 3
|
||||
]
|
||||
|
||||
@doc """
|
||||
Announce (reshare) an activity to the world, using an activity of type `Announce`.
|
||||
"""
|
||||
@spec announce(Actor.t(), ActivityStream.t(), String.t() | nil, boolean, boolean) ::
|
||||
{:ok, Activity.t(), ActivityStream.t()} | {:error, any()}
|
||||
def announce(
|
||||
%Actor{} = actor,
|
||||
object,
|
||||
activity_id \\ nil,
|
||||
local \\ true,
|
||||
public \\ true
|
||||
) do
|
||||
with {:ok, %Actor{id: object_owner_actor_id}} <-
|
||||
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
|
||||
{:ok, %Share{} = _share} <- Share.create(object["id"], actor.id, object_owner_actor_id) do
|
||||
announce_data = make_announce_data(actor, object, activity_id, public)
|
||||
{:ok, activity} = create_activity(announce_data, local)
|
||||
:ok = maybe_federate(activity)
|
||||
{:ok, activity, object}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cancel the announcement of an activity to the world, using an activity of type `Undo` an `Announce`.
|
||||
"""
|
||||
@spec unannounce(Actor.t(), ActivityStream.t(), String.t() | nil, String.t() | nil, boolean) ::
|
||||
{:ok, Activity.t(), ActivityStream.t()}
|
||||
def unannounce(
|
||||
%Actor{} = actor,
|
||||
object,
|
||||
activity_id \\ nil,
|
||||
cancelled_activity_id \\ nil,
|
||||
local \\ true
|
||||
) do
|
||||
announce_activity = make_announce_data(actor, object, cancelled_activity_id)
|
||||
unannounce_data = make_unannounce_data(actor, announce_activity, activity_id)
|
||||
{:ok, unannounce_activity} = create_activity(unannounce_data, local)
|
||||
maybe_federate(unannounce_activity)
|
||||
{:ok, unannounce_activity, object}
|
||||
end
|
||||
end
|
71
lib/federation/activity_pub/actions/create.ex
Normal file
71
lib/federation/activity_pub/actions/create.ex
Normal file
@ -0,0 +1,71 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Create do
|
||||
@moduledoc """
|
||||
Create things
|
||||
"""
|
||||
alias Mobilizon.Tombstone
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Types}
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@type create_entities ::
|
||||
:event | :comment | :discussion | :actor | :todo_list | :todo | :resource | :post
|
||||
|
||||
@doc """
|
||||
Create an activity of type `Create`
|
||||
|
||||
* Creates the object, which returns AS data
|
||||
* Wraps ActivityStreams data into a `Create` activity
|
||||
* Creates an `Mobilizon.Federation.ActivityPub.Activity` from this
|
||||
* Federates (asynchronously) the activity
|
||||
* Returns the activity
|
||||
"""
|
||||
@spec create(create_entities(), map(), boolean, map()) ::
|
||||
{:ok, Activity.t(), Entity.t()}
|
||||
| {:error, :entity_tombstoned | atom() | Ecto.Changeset.t()}
|
||||
def create(type, args, local \\ false, additional \\ %{}) do
|
||||
Logger.debug("creating an activity")
|
||||
Logger.debug(inspect(args))
|
||||
|
||||
case check_for_tombstones(args) do
|
||||
nil ->
|
||||
case do_create(type, args, additional) do
|
||||
{:ok, entity, create_data} ->
|
||||
{:ok, activity} = create_activity(create_data, local)
|
||||
maybe_federate(activity)
|
||||
maybe_relay_if_group_activity(activity)
|
||||
{:ok, activity, entity}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
|
||||
%Tombstone{} ->
|
||||
{:error, :entity_tombstoned}
|
||||
end
|
||||
end
|
||||
|
||||
@spec do_create(create_entities(), map(), map()) ::
|
||||
{:ok, Entity.t(), Activity.t()} | {:error, Ecto.Changeset.t() | atom()}
|
||||
defp do_create(type, args, additional) do
|
||||
case type do
|
||||
:event -> Types.Events.create(args, additional)
|
||||
:comment -> Types.Comments.create(args, additional)
|
||||
:discussion -> Types.Discussions.create(args, additional)
|
||||
:actor -> Types.Actors.create(args, additional)
|
||||
:todo_list -> Types.TodoLists.create(args, additional)
|
||||
:todo -> Types.Todos.create(args, additional)
|
||||
:resource -> Types.Resources.create(args, additional)
|
||||
:post -> Types.Posts.create(args, additional)
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_for_tombstones(map()) :: Tombstone.t() | nil
|
||||
defp check_for_tombstones(%{url: url}), do: Tombstone.find_tombstone(url)
|
||||
defp check_for_tombstones(_), do: nil
|
||||
end
|
33
lib/federation/activity_pub/actions/delete.ex
Normal file
33
lib/federation/activity_pub/actions/delete.ex
Normal file
@ -0,0 +1,33 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Delete do
|
||||
@moduledoc """
|
||||
Delete things
|
||||
"""
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
alias Mobilizon.Federation.ActivityPub.Types.{Entity, Managable, Ownable}
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 2,
|
||||
check_for_actor_key_rotation: 1
|
||||
]
|
||||
|
||||
@doc """
|
||||
Delete an entity, using an activity of type `Delete`
|
||||
"""
|
||||
@spec delete(Entity.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Entity.t()}
|
||||
def delete(object, actor, local \\ true, additional \\ %{}) do
|
||||
with {:ok, activity_data, actor, object} <-
|
||||
Managable.delete(object, actor, local, additional),
|
||||
group <- Ownable.group_actor(object),
|
||||
:ok <- check_for_actor_key_rotation(actor),
|
||||
{:ok, activity} <- create_activity(activity_data, local),
|
||||
:ok <- maybe_federate(activity),
|
||||
:ok <- maybe_relay_if_group_activity(activity, group) do
|
||||
{:ok, activity, object}
|
||||
end
|
||||
end
|
||||
end
|
31
lib/federation/activity_pub/actions/flag.ex
Normal file
31
lib/federation/activity_pub/actions/flag.ex
Normal file
@ -0,0 +1,31 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Flag do
|
||||
@moduledoc """
|
||||
Delete things
|
||||
"""
|
||||
alias Mobilizon.Users
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Types}
|
||||
alias Mobilizon.Web.Email.{Admin, Mailer}
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1
|
||||
]
|
||||
|
||||
@spec flag(map, boolean, map) :: {:ok, Activity.t(), Report.t()} | {:error, Ecto.Changeset.t()}
|
||||
def flag(args, local \\ false, additional \\ %{}) do
|
||||
with {:ok, report, report_as_data} <- Types.Reports.flag(args, local, additional) do
|
||||
{:ok, activity} = create_activity(report_as_data, local)
|
||||
maybe_federate(activity)
|
||||
|
||||
Enum.each(Users.list_moderators(), fn moderator ->
|
||||
moderator
|
||||
|> Admin.report(report)
|
||||
|> Mailer.send_email_later()
|
||||
end)
|
||||
|
||||
{:ok, activity, report}
|
||||
end
|
||||
end
|
||||
end
|
74
lib/federation/activity_pub/actions/follow.ex
Normal file
74
lib/federation/activity_pub/actions/follow.ex
Normal file
@ -0,0 +1,74 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Follow do
|
||||
@moduledoc """
|
||||
Follow people
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
alias Mobilizon.Federation.ActivityPub.Types
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Web.Endpoint
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
make_unfollow_data: 4
|
||||
]
|
||||
|
||||
@doc """
|
||||
Make an actor follow another, using an activity of type `Follow`
|
||||
"""
|
||||
@spec follow(Actor.t(), Actor.t(), String.t() | nil, boolean, map) ::
|
||||
{:ok, Activity.t(), Follower.t()} | {:error, atom | Ecto.Changeset.t() | String.t()}
|
||||
def follow(
|
||||
%Actor{} = follower,
|
||||
%Actor{} = followed,
|
||||
activity_id \\ nil,
|
||||
local \\ true,
|
||||
additional \\ %{}
|
||||
) do
|
||||
if followed.id != follower.id do
|
||||
case Types.Actors.follow(
|
||||
follower,
|
||||
followed,
|
||||
local,
|
||||
Map.merge(additional, %{"activity_id" => activity_id})
|
||||
) do
|
||||
{:ok, activity_data, %Follower{} = follower} ->
|
||||
{:ok, activity} = create_activity(activity_data, local)
|
||||
maybe_federate(activity)
|
||||
{:ok, activity, follower}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
else
|
||||
{:error, "Can't follow yourself"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Make an actor unfollow another, using an activity of type `Undo` a `Follow`.
|
||||
"""
|
||||
@spec unfollow(Actor.t(), Actor.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t(), Follower.t()} | {:error, String.t()}
|
||||
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
||||
with {:ok, %Follower{id: follow_id} = follow} <- Actors.unfollow(followed, follower) do
|
||||
# We recreate the follow activity
|
||||
follow_as_data =
|
||||
Convertible.model_to_as(%{follow | actor: follower, target_actor: followed})
|
||||
|
||||
{:ok, follow_activity} = create_activity(follow_as_data, local)
|
||||
activity_unfollow_id = activity_id || "#{Endpoint.url()}/unfollow/#{follow_id}/activity"
|
||||
|
||||
unfollow_data =
|
||||
make_unfollow_data(follower, followed, follow_activity, activity_unfollow_id)
|
||||
|
||||
{:ok, activity} = create_activity(unfollow_data, local)
|
||||
maybe_federate(activity)
|
||||
{:ok, activity, follow}
|
||||
end
|
||||
end
|
||||
end
|
86
lib/federation/activity_pub/actions/invite.ex
Normal file
86
lib/federation/activity_pub/actions/invite.ex
Normal file
@ -0,0 +1,86 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
|
||||
@moduledoc """
|
||||
Invite people to things
|
||||
"""
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Web.Email.Group
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@spec invite(Actor.t(), Actor.t(), Actor.t(), boolean, map()) ::
|
||||
{:ok, map(), Member.t()} | {:error, :not_able_to_invite | Ecto.Changeset.t()}
|
||||
def invite(
|
||||
%Actor{url: group_url, id: group_id, members_url: members_url} = group,
|
||||
%Actor{url: actor_url, id: actor_id} = actor,
|
||||
%Actor{url: target_actor_url, id: target_actor_id} = _target_actor,
|
||||
local \\ true,
|
||||
additional \\ %{}
|
||||
) do
|
||||
Logger.debug("Handling #{actor_url} invite to #{group_url} sent to #{target_actor_url}")
|
||||
|
||||
if is_able_to_invite?(actor, group) do
|
||||
with {:ok, %Member{url: member_url} = member} <-
|
||||
Actors.create_member(%{
|
||||
parent_id: group_id,
|
||||
actor_id: target_actor_id,
|
||||
role: :invited,
|
||||
invited_by_id: actor_id,
|
||||
url: Map.get(additional, :url)
|
||||
}) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
moderator: actor,
|
||||
subject: "member_invited"
|
||||
)
|
||||
|
||||
{:ok, activity} =
|
||||
create_activity(
|
||||
%{
|
||||
"type" => "Invite",
|
||||
"attributedTo" => group_url,
|
||||
"actor" => actor_url,
|
||||
"object" => group_url,
|
||||
"target" => target_actor_url,
|
||||
"id" => member_url
|
||||
}
|
||||
|> Map.merge(%{"to" => [target_actor_url, members_url], "cc" => [group_url]})
|
||||
|> Map.merge(additional),
|
||||
local
|
||||
)
|
||||
|
||||
maybe_federate(activity)
|
||||
maybe_relay_if_group_activity(activity)
|
||||
Group.send_invite_to_user(member)
|
||||
{:ok, activity, member}
|
||||
end
|
||||
else
|
||||
{:error, :not_able_to_invite}
|
||||
end
|
||||
end
|
||||
|
||||
@spec is_able_to_invite?(Actor.t(), Actor.t()) :: boolean
|
||||
defp is_able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
|
||||
domain: group_domain,
|
||||
id: group_id
|
||||
}) do
|
||||
# If the actor comes from the same domain we trust it
|
||||
if actor_domain == group_domain do
|
||||
true
|
||||
else
|
||||
# If local group, we'll send the invite
|
||||
case Actors.get_member(actor_id, group_id) do
|
||||
{:ok, %Member{} = admin_member} ->
|
||||
Member.is_administrator(admin_member)
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
51
lib/federation/activity_pub/actions/join.ex
Normal file
51
lib/federation/activity_pub/actions/join.ex
Normal file
@ -0,0 +1,51 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Join do
|
||||
@moduledoc """
|
||||
Join things
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Federation.ActivityPub.Types
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1
|
||||
]
|
||||
|
||||
@doc """
|
||||
Join an entity (an event or a group), using an activity of type `Join`
|
||||
"""
|
||||
@spec join(Event.t(), Actor.t(), boolean, map) ::
|
||||
{:ok, Activity.t(), Participant.t()} | {:error, :maximum_attendee_capacity}
|
||||
@spec join(Actor.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Member.t()}
|
||||
def join(entity_to_join, actor_joining, local \\ true, additional \\ %{})
|
||||
|
||||
def join(%Event{} = event, %Actor{} = actor, local, additional) do
|
||||
case Types.Events.join(event, actor, local, additional) do
|
||||
{:ok, activity_data, participant} ->
|
||||
{:ok, activity} = create_activity(activity_data, local)
|
||||
maybe_federate(activity)
|
||||
{:ok, activity, participant}
|
||||
|
||||
{:error, :maximum_attendee_capacity_reached} ->
|
||||
{:error, :maximum_attendee_capacity_reached}
|
||||
|
||||
{:accept, accept} ->
|
||||
accept
|
||||
end
|
||||
end
|
||||
|
||||
def join(%Actor{type: :Group} = group, %Actor{} = actor, local, additional) do
|
||||
with {:ok, activity_data, %Member{} = member} <-
|
||||
Types.Actors.join(group, actor, local, additional),
|
||||
{:ok, activity} <- create_activity(activity_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, member}
|
||||
else
|
||||
{:accept, accept} ->
|
||||
accept
|
||||
end
|
||||
end
|
||||
end
|
104
lib/federation/activity_pub/actions/leave.ex
Normal file
104
lib/federation/activity_pub/actions/leave.ex
Normal file
@ -0,0 +1,104 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
|
||||
@moduledoc """
|
||||
Leave things
|
||||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Events}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Federation.ActivityPub.Audience
|
||||
alias Mobilizon.Web.Endpoint
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@spec leave(Event.t(), Actor.t(), boolean, map) ::
|
||||
{:ok, Activity.t(), Participant.t()}
|
||||
| {:error, :is_only_organizer | :participant_not_found | Ecto.Changeset.t()}
|
||||
@spec leave(Actor.t(), Actor.t(), boolean, map) ::
|
||||
{:ok, Activity.t(), Member.t()} | {:error, atom() | Ecto.Changeset.t()}
|
||||
def leave(object, actor, local \\ true, additional \\ %{})
|
||||
|
||||
@doc """
|
||||
Leave an event or a group
|
||||
"""
|
||||
def leave(
|
||||
%Event{id: event_id, url: event_url} = _event,
|
||||
%Actor{id: actor_id, url: actor_url} = _actor,
|
||||
local,
|
||||
additional
|
||||
) do
|
||||
if Participant.is_not_only_organizer(event_id, actor_id) do
|
||||
{:error, :is_only_organizer}
|
||||
else
|
||||
case Mobilizon.Events.get_participant(
|
||||
event_id,
|
||||
actor_id,
|
||||
Map.get(additional, :metadata, %{})
|
||||
) do
|
||||
{:ok, %Participant{} = participant} ->
|
||||
case Events.delete_participant(participant) do
|
||||
{:ok, %{participant: %Participant{} = participant}} ->
|
||||
leave_data = %{
|
||||
"type" => "Leave",
|
||||
# If it's an exclusion it should be something else
|
||||
"actor" => actor_url,
|
||||
"object" => event_url,
|
||||
"id" => "#{Endpoint.url()}/leave/event/#{participant.id}"
|
||||
}
|
||||
|
||||
audience = Audience.get_audience(participant)
|
||||
{:ok, activity} = create_activity(Map.merge(leave_data, audience), local)
|
||||
maybe_federate(activity)
|
||||
{:ok, activity, participant}
|
||||
|
||||
{:error, _type, %Ecto.Changeset{} = err, _} ->
|
||||
{:error, err}
|
||||
end
|
||||
|
||||
{:error, :participant_not_found} ->
|
||||
{:error, :participant_not_found}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def leave(
|
||||
%Actor{type: :Group, id: group_id, url: group_url, members_url: group_members_url},
|
||||
%Actor{id: actor_id, url: actor_url},
|
||||
local,
|
||||
additional
|
||||
) do
|
||||
with {:member, {:ok, %Member{id: member_id} = member}} <-
|
||||
{:member, Actors.get_member(actor_id, group_id)},
|
||||
{:is_not_only_admin, true} <-
|
||||
{:is_not_only_admin,
|
||||
Map.get(additional, :force_member_removal, false) ||
|
||||
!Actors.is_only_administrator?(member_id, group_id)},
|
||||
{:delete, {:ok, %Member{} = member}} <- {:delete, Actors.delete_member(member)} do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit")
|
||||
|
||||
leave_data = %{
|
||||
"to" => [group_members_url],
|
||||
"cc" => [group_url],
|
||||
"attributedTo" => group_url,
|
||||
"type" => "Leave",
|
||||
"actor" => actor_url,
|
||||
"object" => group_url
|
||||
}
|
||||
|
||||
{:ok, activity} = create_activity(leave_data, local)
|
||||
maybe_federate(activity)
|
||||
maybe_relay_if_group_activity(activity)
|
||||
{:ok, activity, member}
|
||||
else
|
||||
{:member, nil} -> {:error, :member_not_found}
|
||||
{:is_not_only_admin, false} -> {:error, :is_not_only_admin}
|
||||
{:error, %Ecto.Changeset{} = err} -> {:error, err}
|
||||
end
|
||||
end
|
||||
end
|
30
lib/federation/activity_pub/actions/move.ex
Normal file
30
lib/federation/activity_pub/actions/move.ex
Normal file
@ -0,0 +1,30 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Move do
|
||||
@moduledoc """
|
||||
Move things
|
||||
"""
|
||||
alias Mobilizon.Resources.Resource
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Types}
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1
|
||||
]
|
||||
|
||||
@spec move(:resource, Resource.t(), map, boolean, map) ::
|
||||
{:ok, Activity.t(), Resource.t()} | {:error, Ecto.Changeset.t() | atom()}
|
||||
def move(type, old_entity, args, local \\ false, additional \\ %{}) do
|
||||
Logger.debug("We're moving something")
|
||||
Logger.debug(inspect(args))
|
||||
|
||||
with {:ok, entity, update_data} <-
|
||||
(case type do
|
||||
:resource -> Types.Resources.move(old_entity, args, additional)
|
||||
end) do
|
||||
{:ok, activity} = create_activity(update_data, local)
|
||||
maybe_federate(activity)
|
||||
{:ok, activity, entity}
|
||||
end
|
||||
end
|
||||
end
|
127
lib/federation/activity_pub/actions/reject.ex
Normal file
127
lib/federation/activity_pub/actions/reject.ex
Normal file
@ -0,0 +1,127 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Reject do
|
||||
@moduledoc """
|
||||
Reject things
|
||||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Events}
|
||||
alias Mobilizon.Actors.{Actor, Follower, Member}
|
||||
alias Mobilizon.Events.Participant
|
||||
alias Mobilizon.Federation.ActivityPub.Actions.Accept
|
||||
alias Mobilizon.Federation.ActivityPub.Audience
|
||||
alias Mobilizon.Federation.ActivityStream
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Web.Endpoint
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@spec reject(Accept.acceptable_types(), Accept.acceptable_entities(), boolean, map) ::
|
||||
{:ok, ActivityStream.t(), Accept.acceptable_entities()}
|
||||
def reject(type, entity, local \\ true, additional \\ %{}) do
|
||||
{:ok, entity, update_data} =
|
||||
case type do
|
||||
:join -> reject_join(entity, additional)
|
||||
:follow -> reject_follow(entity, additional)
|
||||
:invite -> reject_invite(entity, additional)
|
||||
end
|
||||
|
||||
with {:ok, activity} <- create_activity(update_data, local),
|
||||
:ok <- maybe_federate(activity),
|
||||
:ok <- maybe_relay_if_group_activity(activity) do
|
||||
{:ok, activity, entity}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an activity")
|
||||
Logger.debug(inspect(err))
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@spec reject_join(Participant.t(), map()) :: {:ok, Participant.t(), Activity.t()} | any()
|
||||
defp reject_join(%Participant{} = participant, additional) do
|
||||
with {:ok, %Participant{} = participant} <-
|
||||
Events.update_participant(participant, %{role: :rejected}),
|
||||
Absinthe.Subscription.publish(Endpoint, participant.actor,
|
||||
event_person_participation_changed: participant.actor.id
|
||||
),
|
||||
participant_as_data <- Convertible.model_to_as(participant),
|
||||
audience <-
|
||||
participant
|
||||
|> Audience.get_audience()
|
||||
|> Map.merge(additional),
|
||||
reject_data <- %{
|
||||
"type" => "Reject",
|
||||
"object" => participant_as_data
|
||||
},
|
||||
update_data <-
|
||||
reject_data
|
||||
|> Map.merge(audience)
|
||||
|> Map.merge(%{
|
||||
"id" => "#{Endpoint.url()}/reject/join/#{participant.id}"
|
||||
}) do
|
||||
{:ok, participant, update_data}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an update activity")
|
||||
Logger.debug(inspect(err))
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@spec reject_follow(Follower.t(), map()) :: {:ok, Follower.t(), Activity.t()} | any()
|
||||
defp reject_follow(%Follower{} = follower, additional) do
|
||||
with {:ok, %Follower{} = follower} <- Actors.delete_follower(follower),
|
||||
follower_as_data <- Convertible.model_to_as(follower),
|
||||
audience <-
|
||||
follower.actor |> Audience.get_audience() |> Map.merge(additional),
|
||||
reject_data <- %{
|
||||
"to" => [follower.actor.url],
|
||||
"type" => "Reject",
|
||||
"actor" => follower.target_actor.url,
|
||||
"object" => follower_as_data
|
||||
},
|
||||
update_data <-
|
||||
audience
|
||||
|> Map.merge(reject_data)
|
||||
|> Map.merge(%{
|
||||
"id" => "#{Endpoint.url()}/reject/follow/#{follower.id}"
|
||||
}) do
|
||||
{:ok, follower, update_data}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an update activity")
|
||||
Logger.debug(inspect(err))
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@spec reject_invite(Member.t(), map()) :: {:ok, Member.t(), Activity.t()} | any
|
||||
defp reject_invite(
|
||||
%Member{invited_by_id: invited_by_id, actor_id: actor_id} = member,
|
||||
_additional
|
||||
) do
|
||||
with %Actor{} = inviter <- Actors.get_actor(invited_by_id),
|
||||
%Actor{url: actor_url} <- Actors.get_actor(actor_id),
|
||||
{:ok, %Member{url: member_url, id: member_id} = member} <-
|
||||
Actors.delete_member(member),
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
subject: "member_rejected_invitation"
|
||||
),
|
||||
accept_data <- %{
|
||||
"type" => "Reject",
|
||||
"actor" => actor_url,
|
||||
"attributedTo" => member.parent.url,
|
||||
"to" => [inviter.url, member.parent.members_url],
|
||||
"cc" => [member.parent.url],
|
||||
"object" => member_url,
|
||||
"id" => "#{Endpoint.url()}/reject/invite/member/#{member_id}"
|
||||
} do
|
||||
{:ok, member, accept_data}
|
||||
end
|
||||
end
|
||||
end
|
56
lib/federation/activity_pub/actions/remove.ex
Normal file
56
lib/federation/activity_pub/actions/remove.ex
Normal file
@ -0,0 +1,56 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Remove do
|
||||
@moduledoc """
|
||||
Remove things
|
||||
"""
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
alias Mobilizon.Web.Email.Group
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@doc """
|
||||
Remove an activity, using an activity of type `Remove`
|
||||
"""
|
||||
@spec remove(Member.t(), Actor.t(), Actor.t(), boolean, map) ::
|
||||
{:ok, Activity.t(), Member.t()} | {:error, :member_not_found | Ecto.Changeset.t()}
|
||||
def remove(
|
||||
%Member{} = member,
|
||||
%Actor{type: :Group, url: group_url, members_url: group_members_url},
|
||||
%Actor{url: moderator_url} = moderator,
|
||||
local,
|
||||
_additional \\ %{}
|
||||
) do
|
||||
with {:ok, %Member{id: member_id}} <- Actors.update_member(member, %{role: :rejected}),
|
||||
%Member{} = member <- Actors.get_member(member_id) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member,
|
||||
moderator: moderator,
|
||||
subject: "member_removed"
|
||||
)
|
||||
|
||||
Group.send_notification_to_removed_member(member)
|
||||
|
||||
remove_data = %{
|
||||
"to" => [group_members_url],
|
||||
"type" => "Remove",
|
||||
"actor" => moderator_url,
|
||||
"object" => member.url,
|
||||
"origin" => group_url
|
||||
}
|
||||
|
||||
{:ok, activity} = create_activity(remove_data, local)
|
||||
maybe_federate(activity)
|
||||
maybe_relay_if_group_activity(activity)
|
||||
{:ok, activity, member}
|
||||
else
|
||||
nil -> {:error, :member_not_found}
|
||||
{:error, %Ecto.Changeset{} = err} -> {:error, err}
|
||||
end
|
||||
end
|
||||
end
|
44
lib/federation/activity_pub/actions/update.ex
Normal file
44
lib/federation/activity_pub/actions/update.ex
Normal file
@ -0,0 +1,44 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Actions.Update do
|
||||
@moduledoc """
|
||||
Update things
|
||||
"""
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Managable
|
||||
require Logger
|
||||
|
||||
import Mobilizon.Federation.ActivityPub.Utils,
|
||||
only: [
|
||||
create_activity: 2,
|
||||
maybe_federate: 1,
|
||||
maybe_relay_if_group_activity: 1
|
||||
]
|
||||
|
||||
@doc """
|
||||
Create an activity of type `Update`
|
||||
|
||||
* Updates the object, which returns AS data
|
||||
* Wraps ActivityStreams data into a `Update` activity
|
||||
* Creates an `Mobilizon.Federation.ActivityPub.Activity` from this
|
||||
* Federates (asynchronously) the activity
|
||||
* Returns the activity
|
||||
"""
|
||||
@spec update(Entity.t(), map(), boolean, map()) ::
|
||||
{:ok, Activity.t(), Entity.t()} | {:error, atom() | Ecto.Changeset.t()}
|
||||
def update(old_entity, args, local \\ false, additional \\ %{}) do
|
||||
Logger.debug("updating an activity")
|
||||
Logger.debug(inspect(args))
|
||||
|
||||
case Managable.update(old_entity, args, additional) do
|
||||
{:ok, entity, update_data} ->
|
||||
{:ok, activity} = create_activity(update_data, local)
|
||||
maybe_federate(activity)
|
||||
maybe_relay_if_group_activity(activity)
|
||||
{:ok, activity, entity}
|
||||
|
||||
{:error, err} ->
|
||||
Logger.error("Something went wrong while creating an activity")
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
end
|
@ -12,64 +12,34 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||
|
||||
alias Mobilizon.{
|
||||
Actors,
|
||||
Config,
|
||||
Discussions,
|
||||
Events,
|
||||
Posts,
|
||||
Resources,
|
||||
Share,
|
||||
Users
|
||||
Resources
|
||||
}
|
||||
|
||||
alias Mobilizon.Actors.{Actor, Follower, Member}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Tombstone
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.{
|
||||
Activity,
|
||||
Audience,
|
||||
Federator,
|
||||
Fetcher,
|
||||
Preloader,
|
||||
Refresher,
|
||||
Relay,
|
||||
Transmogrifier,
|
||||
Types,
|
||||
Visibility
|
||||
Relay
|
||||
}
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Types.{Entity, Managable, Ownable}
|
||||
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Federation.HTTPSignatures.Signature
|
||||
|
||||
alias Mobilizon.Service.Notifications.Scheduler
|
||||
alias Mobilizon.Storage.Page
|
||||
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Email.{Admin, Group, Mailer}
|
||||
|
||||
require Logger
|
||||
|
||||
@public_ap_adress "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
# Wraps an object into an activity
|
||||
@spec create_activity(map(), boolean()) :: {:ok, Activity.t()}
|
||||
defp create_activity(map, local) when is_map(map) do
|
||||
with map <- lazy_put_activity_defaults(map) do
|
||||
{:ok,
|
||||
%Activity{
|
||||
data: map,
|
||||
local: local,
|
||||
actor: map["actor"],
|
||||
recipients: get_recipients(map)
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetch an object from an URL, from our local database of events and comments, then eventually remote
|
||||
"""
|
||||
@ -79,33 +49,32 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||
def fetch_object_from_url(url, options \\ []) do
|
||||
Logger.info("Fetching object from url #{url}")
|
||||
|
||||
with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")},
|
||||
{:existing, nil} <-
|
||||
{:existing, Tombstone.find_tombstone(url)},
|
||||
{:existing, nil} <- {:existing, Events.get_event_by_url(url)},
|
||||
{:existing, nil} <-
|
||||
{:existing, Discussions.get_discussion_by_url(url)},
|
||||
{:existing, nil} <- {:existing, Discussions.get_comment_from_url(url)},
|
||||
{:existing, nil} <- {:existing, Resources.get_resource_by_url(url)},
|
||||
{:existing, nil} <- {:existing, Posts.get_post_by_url(url)},
|
||||
{:existing, nil} <-
|
||||
{:existing, Actors.get_actor_by_url_2(url)},
|
||||
{:existing, nil} <- {:existing, Actors.get_member_by_url(url)},
|
||||
:ok <- Logger.info("Data for URL not found anywhere, going to fetch it"),
|
||||
{:ok, _activity, entity} <- Fetcher.fetch_and_create(url, options) do
|
||||
Logger.debug("Going to preload the new entity")
|
||||
Preloader.maybe_preload(entity)
|
||||
if String.starts_with?(url, "http") do
|
||||
with {:existing, nil} <-
|
||||
{:existing, Tombstone.find_tombstone(url)},
|
||||
{:existing, nil} <- {:existing, Events.get_event_by_url(url)},
|
||||
{:existing, nil} <-
|
||||
{:existing, Discussions.get_discussion_by_url(url)},
|
||||
{:existing, nil} <- {:existing, Discussions.get_comment_from_url(url)},
|
||||
{:existing, nil} <- {:existing, Resources.get_resource_by_url(url)},
|
||||
{:existing, nil} <- {:existing, Posts.get_post_by_url(url)},
|
||||
{:existing, nil} <-
|
||||
{:existing, Actors.get_actor_by_url_2(url)},
|
||||
{:existing, nil} <- {:existing, Actors.get_member_by_url(url)},
|
||||
:ok <- Logger.info("Data for URL not found anywhere, going to fetch it"),
|
||||
{:ok, _activity, entity} <- Fetcher.fetch_and_create(url, options) do
|
||||
Logger.debug("Going to preload the new entity")
|
||||
Preloader.maybe_preload(entity)
|
||||
else
|
||||
{:existing, entity} ->
|
||||
handle_existing_entity(url, entity, options)
|
||||
|
||||
{:error, e} ->
|
||||
Logger.warn("Something failed while fetching url #{url} #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
else
|
||||
{:existing, entity} ->
|
||||
handle_existing_entity(url, entity, options)
|
||||
|
||||
{:error, e} ->
|
||||
Logger.warn("Something failed while fetching url #{url} #{inspect(e)}")
|
||||
{:error, e}
|
||||
|
||||
e ->
|
||||
Logger.warn("Something failed while fetching url #{url} #{inspect(e)}")
|
||||
{:error, e}
|
||||
{:error, :url_not_http}
|
||||
end
|
||||
end
|
||||
|
||||
@ -132,7 +101,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||
end
|
||||
|
||||
@spec refresh_entity(String.t(), struct(), Keyword.t()) ::
|
||||
{:ok, struct()} | {:error, atom(), struct()} | {:error, String.t()}
|
||||
{:ok, struct()} | {:error, atom(), struct()} | {:error, atom()}
|
||||
defp refresh_entity(url, entity, options) do
|
||||
force_fetch = Keyword.get(options, :force, false)
|
||||
|
||||
@ -149,609 +118,14 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||
{:error, :http_not_found} ->
|
||||
{:error, :http_not_found, entity}
|
||||
|
||||
{:error, "Object origin check failed"} ->
|
||||
{:error, "Object origin check failed"}
|
||||
end
|
||||
else
|
||||
{:ok, entity}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create an activity of type `Create`
|
||||
|
||||
* Creates the object, which returns AS data
|
||||
* Wraps ActivityStreams data into a `Create` activity
|
||||
* Creates an `Mobilizon.Federation.ActivityPub.Activity` from this
|
||||
* Federates (asynchronously) the activity
|
||||
* Returns the activity
|
||||
"""
|
||||
@spec create(atom(), map(), boolean, map()) :: {:ok, Activity.t(), Entity.entities()} | any()
|
||||
def create(type, args, local \\ false, additional \\ %{}) do
|
||||
Logger.debug("creating an activity")
|
||||
Logger.debug(inspect(args))
|
||||
|
||||
with {:tombstone, nil} <- {:tombstone, check_for_tombstones(args)},
|
||||
{:ok, entity, create_data} <-
|
||||
(case type do
|
||||
:event -> Types.Events.create(args, additional)
|
||||
:comment -> Types.Comments.create(args, additional)
|
||||
:discussion -> Types.Discussions.create(args, additional)
|
||||
:actor -> Types.Actors.create(args, additional)
|
||||
:todo_list -> Types.TodoLists.create(args, additional)
|
||||
:todo -> Types.Todos.create(args, additional)
|
||||
:resource -> Types.Resources.create(args, additional)
|
||||
:post -> Types.Posts.create(args, additional)
|
||||
end),
|
||||
{:ok, activity} <- create_activity(create_data, local),
|
||||
:ok <- maybe_federate(activity),
|
||||
:ok <- maybe_relay_if_group_activity(activity) do
|
||||
{:ok, activity, entity}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an activity")
|
||||
Logger.debug(inspect(err))
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create an activity of type `Update`
|
||||
|
||||
* Updates the object, which returns AS data
|
||||
* Wraps ActivityStreams data into a `Update` activity
|
||||
* Creates an `Mobilizon.Federation.ActivityPub.Activity` from this
|
||||
* Federates (asynchronously) the activity
|
||||
* Returns the activity
|
||||
"""
|
||||
@spec update(Entity.entities(), map(), boolean, map()) ::
|
||||
{:ok, Activity.t(), Entity.entities()} | {:error, any()}
|
||||
def update(old_entity, args, local \\ false, additional \\ %{}) do
|
||||
Logger.debug("updating an activity")
|
||||
Logger.debug(inspect(args))
|
||||
|
||||
case Managable.update(old_entity, args, additional) do
|
||||
{:ok, entity, update_data} ->
|
||||
{:ok, activity} = create_activity(update_data, local)
|
||||
maybe_federate(activity)
|
||||
maybe_relay_if_group_activity(activity)
|
||||
{:ok, activity, entity}
|
||||
|
||||
{:error, err} ->
|
||||
Logger.error("Something went wrong while creating an activity")
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
@type acceptable_types :: :join | :follow | :invite
|
||||
@type acceptable_entities ::
|
||||
accept_join_entities | accept_follow_entities | accept_invite_entities
|
||||
|
||||
@spec accept(acceptable_types, acceptable_entities, boolean, map) ::
|
||||
{:ok, ActivityStream.t(), acceptable_entities}
|
||||
def accept(type, entity, local \\ true, additional \\ %{}) do
|
||||
Logger.debug("We're accepting something")
|
||||
|
||||
{:ok, entity, update_data} =
|
||||
case type do
|
||||
:join -> accept_join(entity, additional)
|
||||
:follow -> accept_follow(entity, additional)
|
||||
:invite -> accept_invite(entity, additional)
|
||||
end
|
||||
|
||||
with {:ok, activity} <- create_activity(update_data, local),
|
||||
:ok <- maybe_federate(activity),
|
||||
:ok <- maybe_relay_if_group_activity(activity) do
|
||||
{:ok, activity, entity}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an activity")
|
||||
Logger.debug(inspect(err))
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@spec reject(acceptable_types, acceptable_entities, boolean, map) ::
|
||||
{:ok, ActivityStream.t(), acceptable_entities}
|
||||
def reject(type, entity, local \\ true, additional \\ %{}) do
|
||||
{:ok, entity, update_data} =
|
||||
case type do
|
||||
:join -> reject_join(entity, additional)
|
||||
:follow -> reject_follow(entity, additional)
|
||||
:invite -> reject_invite(entity, additional)
|
||||
end
|
||||
|
||||
with {:ok, activity} <- create_activity(update_data, local),
|
||||
:ok <- maybe_federate(activity),
|
||||
:ok <- maybe_relay_if_group_activity(activity) do
|
||||
{:ok, activity, entity}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an activity")
|
||||
Logger.debug(inspect(err))
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@spec announce(Actor.t(), ActivityStream.t(), String.t() | nil, boolean, boolean) ::
|
||||
{:ok, Activity.t(), ActivityStream.t()} | {:error, any()}
|
||||
def announce(
|
||||
%Actor{} = actor,
|
||||
object,
|
||||
activity_id \\ nil,
|
||||
local \\ true,
|
||||
public \\ true
|
||||
) do
|
||||
with {:ok, %Actor{id: object_owner_actor_id}} <-
|
||||
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
|
||||
{:ok, %Share{} = _share} <- Share.create(object["id"], actor.id, object_owner_actor_id),
|
||||
announce_data <- make_announce_data(actor, object, activity_id, public),
|
||||
{:ok, activity} <- create_activity(announce_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
else
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
@spec unannounce(Actor.t(), ActivityStream.t(), String.t() | nil, String.t() | nil, boolean) ::
|
||||
{:ok, Activity.t(), ActivityStream.t()}
|
||||
def unannounce(
|
||||
%Actor{} = actor,
|
||||
object,
|
||||
activity_id \\ nil,
|
||||
cancelled_activity_id \\ nil,
|
||||
local \\ true
|
||||
) do
|
||||
with announce_activity <- make_announce_data(actor, object, cancelled_activity_id),
|
||||
unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
|
||||
{:ok, unannounce_activity} <- create_activity(unannounce_data, local),
|
||||
:ok <- maybe_federate(unannounce_activity) do
|
||||
{:ok, unannounce_activity, object}
|
||||
else
|
||||
_e -> {:ok, object}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Make an actor follow another
|
||||
"""
|
||||
@spec follow(Actor.t(), Actor.t(), String.t() | nil, boolean, map) ::
|
||||
{:ok, Activity.t(), Follower.t()} | {:error, atom | Ecto.Changeset.t() | String.t()}
|
||||
def follow(
|
||||
%Actor{} = follower,
|
||||
%Actor{} = followed,
|
||||
activity_id \\ nil,
|
||||
local \\ true,
|
||||
additional \\ %{}
|
||||
) do
|
||||
if followed.id != follower.id do
|
||||
case Types.Actors.follow(
|
||||
follower,
|
||||
followed,
|
||||
local,
|
||||
Map.merge(additional, %{"activity_id" => activity_id})
|
||||
) do
|
||||
{:ok, activity_data, %Follower{} = follower} ->
|
||||
{:ok, activity} = create_activity(activity_data, local)
|
||||
maybe_federate(activity)
|
||||
{:ok, activity, follower}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
else
|
||||
{:error, "Can't follow yourself"}
|
||||
{:ok, entity}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Make an actor unfollow another
|
||||
"""
|
||||
@spec unfollow(Actor.t(), Actor.t(), String.t() | nil, boolean()) ::
|
||||
{:ok, Activity.t(), Follower.t()} | {:error, String.t()}
|
||||
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
||||
with {:ok, %Follower{id: follow_id} = follow} <- Actors.unfollow(followed, follower),
|
||||
# We recreate the follow activity
|
||||
follow_as_data <-
|
||||
Convertible.model_to_as(%{follow | actor: follower, target_actor: followed}),
|
||||
{:ok, follow_activity} <- create_activity(follow_as_data, local),
|
||||
activity_unfollow_id <-
|
||||
activity_id || "#{Endpoint.url()}/unfollow/#{follow_id}/activity",
|
||||
unfollow_data <-
|
||||
make_unfollow_data(follower, followed, follow_activity, activity_unfollow_id),
|
||||
{:ok, activity} <- create_activity(unfollow_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, follow}
|
||||
else
|
||||
err ->
|
||||
Logger.debug("Error while unfollowing an actor #{inspect(err)}")
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete(Entity.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Entity.t()}
|
||||
def delete(object, actor, local \\ true, additional \\ %{}) do
|
||||
with {:ok, activity_data, actor, object} <-
|
||||
|