mobilizon/lib/federation/activity_pub/actions/accept.ex
Thomas Citharel 6eba531c89
Allow group admins to moderate new members
Closes #881

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2021-11-12 17:17:05 +01:00

207 lines
6.8 KiB
Elixir

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.Email.Member, as: EmailMember
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 | :member
@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} | {:error, Ecto.Changeset.t()}
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)
:member -> accept_member(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 accept_member(Member.t(), map()) ::
{:ok, Member.t(), Activity.t()} | {:error, Ecto.Changeset.t()}
defp accept_member(
%Member{actor_id: actor_id, actor: actor, parent: %Actor{} = group} = member,
%{moderator: %Actor{url: actor_url} = moderator}
) do
with %Actor{} <- 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_approved",
moderator: moderator
)
Absinthe.Subscription.publish(Endpoint, actor,
group_membership_changed: [Actor.preferred_username_and_domain(group), actor_id]
)
EmailMember.send_notification_to_approved_member(member)
Cachex.del(:activity_pub, "member_#{member_id}")
maybe_refresh_group(member)
accept_data = %{
"type" => "Accept",
"attributedTo" => member.parent.url,
"to" => [member.parent.members_url],
"cc" => [member.parent.url],
"actor" => actor_url,
"object" => Convertible.model_to_as(member),
"id" => "#{Endpoint.url()}/accept/member/#{member_id}"
}
{:ok, member, accept_data}
end
end
@spec maybe_refresh_group(Member.t()) :: {:ok, Actor.t()} | {:error, atom()} | {:error}
defp maybe_refresh_group(%Member{
parent: %Actor{} = group
}),
do: Refresher.refresh_profile(group)
end