mobilizon.chapril.org-mobil.../lib/federation/activity_pub/types/events.ex

263 lines
9.3 KiB
Elixir

defmodule Mobilizon.Federation.ActivityPub.Types.Events do
@moduledoc false
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Events, as: EventsManager
alias Mobilizon.Events.{Event, Participant, ParticipantRole}
alias Mobilizon.Federation.{ActivityPub, ActivityStream}
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
alias Mobilizon.Federation.ActivityPub.Types.Entity
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
alias Mobilizon.Service.Activity.Event, as: EventActivity
alias Mobilizon.Service.Formatter.HTML
alias Mobilizon.Service.LanguageDetection
alias Mobilizon.Service.Notifications.Scheduler
alias Mobilizon.Share
alias Mobilizon.Tombstone
import Mobilizon.Federation.ActivityPub.Utils, only: [make_create_data: 2, make_update_data: 2]
require Logger
@behaviour Entity
@impl Entity
@spec create(map(), map()) :: {:ok, Event.t(), ActivityStream.t()}
def create(args, additional) do
with args <- prepare_args_for_event(args),
{:ok, %Event{} = event} <- EventsManager.create_event(args),
{:ok, _} <-
EventActivity.insert_activity(event, subject: "event_created"),
event_as_data <- Convertible.model_to_as(event),
audience <-
Audience.get_audience(event),
create_data <-
make_create_data(event_as_data, Map.merge(audience, additional)) do
{:ok, event, create_data}
end
end
@impl Entity
@spec update(Event.t(), map(), map()) :: {:ok, Event.t(), ActivityStream.t()}
def update(%Event{} = old_event, args, additional) do
with args <- prepare_args_for_event(args),
{:ok, %Event{} = new_event} <- EventsManager.update_event(old_event, args),
{:ok, _} <-
EventActivity.insert_activity(new_event, subject: "event_updated"),
{:ok, true} <- Cachex.del(:activity_pub, "event_#{new_event.uuid}"),
event_as_data <- Convertible.model_to_as(new_event),
audience <-
Audience.get_audience(new_event),
update_data <- make_update_data(event_as_data, Map.merge(audience, additional)) do
{:ok, new_event, update_data}
else
err ->
Logger.error("Something went wrong while creating an update activity")
Logger.debug(inspect(err))
err
end
end
@impl Entity
@spec delete(Event.t(), Actor.t(), boolean, map()) ::
{:ok, ActivityStream.t(), Actor.t(), Event.t()}
def delete(%Event{url: url} = event, %Actor{} = actor, _local, _additionnal) do
activity_data = %{
"type" => "Delete",
"actor" => actor.url,
"object" => Convertible.model_to_as(event),
"to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"],
"id" => url <> "/delete"
}
with audience <-
Audience.get_audience(event),
{:ok, %Event{} = event} <- EventsManager.delete_event(event),
{:ok, _} <-
EventActivity.insert_activity(event, subject: "event_deleted"),
{:ok, true} <- Cachex.del(:activity_pub, "event_#{event.uuid}"),
{:ok, %Tombstone{} = _tombstone} <-
Tombstone.create_tombstone(%{uri: event.url, actor_id: actor.id}) do
Share.delete_all_by_uri(event.url)
{:ok, Map.merge(activity_data, audience), actor, event}
end
end
@spec actor(Event.t()) :: Actor.t() | nil
def actor(%Event{organizer_actor: %Actor{} = actor}), do: actor
def actor(%Event{organizer_actor_id: organizer_actor_id}),
do: Actors.get_actor(organizer_actor_id)
def actor(_), do: nil
@spec group_actor(Event.t()) :: Actor.t() | nil
def group_actor(%Event{attributed_to: %Actor{} = group}), do: group
def group_actor(%Event{attributed_to_id: attributed_to_id}) when not is_nil(attributed_to_id),
do: Actors.get_actor(attributed_to_id)
def group_actor(_), do: nil
@spec permissions(Event.t()) :: Permission.t()
def permissions(%Event{draft: draft, attributed_to_id: _attributed_to_id}) do
%Permission{
access: if(draft, do: nil, else: :member),
create: :moderator,
update: :moderator,
delete: :moderator
}
end
@spec join(Event.t(), Actor.t(), boolean, map) ::
{:ok, ActivityStreams.t(), Participant.t()}
| {:error, :maximum_attendee_capacity_reached}
def join(%Event{} = event, %Actor{} = actor, _local, additional) do
with {:maximum_attendee_capacity, true} <-
{:maximum_attendee_capacity, check_attendee_capacity?(event)},
role <-
additional
|> Map.get(:metadata, %{})
|> Map.get(:role, Mobilizon.Events.get_default_participant_role(event)),
{:ok, %Participant{} = participant} <-
Mobilizon.Events.create_participant(%{
role: role,
event_id: event.id,
actor_id: actor.id,
url: Map.get(additional, :url),
metadata:
additional
|> Map.get(:metadata, %{})
|> Map.update(:message, nil, &String.trim(HTML.strip_tags(&1)))
}),
join_data <- Convertible.model_to_as(participant),
audience <-
Audience.get_audience(participant) do
approve_if_default_role_is_participant(
event,
Map.merge(join_data, audience),
participant,
role
)
else
{:maximum_attendee_capacity, false} ->
{:error, :maximum_attendee_capacity_reached}
end
end
@spec check_attendee_capacity?(Event.t()) :: boolean
defp check_attendee_capacity?(%Event{options: options} = event) do
with maximum_attendee_capacity <-
Map.get(options, :maximum_attendee_capacity) || 0 do
maximum_attendee_capacity == 0 ||
Mobilizon.Events.count_participant_participants(event.id) < maximum_attendee_capacity
end
end
# Set the participant to approved if the default role for new participants is :participant
@spec approve_if_default_role_is_participant(
Event.t(),
ActivityStreams.t(),
Participant.t(),
ParticipantRole.t()
) :: {:ok, ActivityStreams.t(), Participant.t()}
defp approve_if_default_role_is_participant(event, activity_data, participant, role) do
case event do
%Event{attributed_to: %Actor{id: group_id, url: group_url}} ->
case Actors.get_single_group_moderator_actor(group_id) do
%Actor{} = actor ->
do_approve(event, activity_data, participant, role, %{
"actor" => actor.url,
"attributedTo" => group_url
})
_ ->
{:ok, activity_data, participant}
end
%Event{local: true} ->
do_approve(event, activity_data, participant, role, %{
"actor" => event.organizer_actor.url
})
_ ->
{:ok, activity_data, participant}
end
end
@spec do_approve(Event.t(), ActivityStreams.t(), Particpant.t(), ParticipantRole.t(), map()) ::
{:accept, any} | {:ok, ActivityStreams.t(), Participant.t()}
defp do_approve(event, activity_data, participant, role, additionnal) do
cond do
Mobilizon.Events.get_default_participant_role(event) == :participant &&
role == :participant ->
{:accept,
ActivityPub.accept(
:join,
participant,
true,
additionnal
)}
Mobilizon.Events.get_default_participant_role(event) == :not_approved &&
role == :not_approved ->
Scheduler.pending_participation_notification(event)
{:ok, activity_data, participant}
true ->
{:ok, activity_data, participant}
end
end
# Prepare and sanitize arguments for events
@spec prepare_args_for_event(map) :: map
defp prepare_args_for_event(args) do
# If title is not set: we are not updating it
args =
if Map.has_key?(args, :title) && !is_nil(args.title),
do: Map.update(args, :title, "", &String.trim/1),
else: args
# If we've been given a description (we might not get one if updating)
# sanitize it, HTML it, and extract tags & mentions from it
args =
if Map.has_key?(args, :description) && !is_nil(args.description) do
{description, mentions, tags} =
APIUtils.make_content_html(
String.trim(args.description),
Map.get(args, :tags, []),
"text/html"
)
mentions = ConverterUtils.fetch_mentions(Map.get(args, :mentions, []) ++ mentions)
Map.merge(args, %{
description: description,
mentions: mentions,
tags: tags
})
else
args
end
# Check that we can only allow anonymous participation if our instance allows it
{_, options} =
Map.get_and_update(
Map.get(args, :options, %{anonymous_participation: false}),
:anonymous_participation,
fn value ->
{value, value && Mobilizon.Config.anonymous_participation?()}
end
)
args
|> Map.put(:options, options)
|> Map.put_new(:language, "und")
|> Map.update!(:language, fn lang ->
if lang == "und", do: LanguageDetection.detect(:event, args), else: lang
end)
|> Map.update(:tags, [], &ConverterUtils.fetch_tags/1)
|> Map.update(:contacts, [], &ConverterUtils.fetch_actors/1)
end
end