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