Merge branch 'feature/federate-participations' into 'master'
Federate participations See merge request framasoft/mobilizon!166
This commit is contained in:
commit
4bc70d5070
|
@ -58,6 +58,10 @@ defmodule Mobilizon.Actors do
|
||||||
Repo.get!(Actor, id)
|
Repo.get!(Actor, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_actor(id) do
|
||||||
|
Repo.get(Actor, id)
|
||||||
|
end
|
||||||
|
|
||||||
# Get actor by ID and preload organized events, followers and followings
|
# Get actor by ID and preload organized events, followers and followings
|
||||||
@spec get_actor_with_everything(integer()) :: Ecto.Query.t()
|
@spec get_actor_with_everything(integer()) :: Ecto.Query.t()
|
||||||
defp do_get_actor_with_everything(id) do
|
defp do_get_actor_with_everything(id) do
|
||||||
|
|
|
@ -214,6 +214,27 @@ defmodule Mobilizon.Events do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single event, with all associations loaded.
|
||||||
|
"""
|
||||||
|
def get_event_full(id) do
|
||||||
|
case Repo.get(Event, id) do
|
||||||
|
%Event{} = event ->
|
||||||
|
{:ok,
|
||||||
|
Repo.preload(event, [
|
||||||
|
:organizer_actor,
|
||||||
|
:sessions,
|
||||||
|
:tracks,
|
||||||
|
:tags,
|
||||||
|
:participants,
|
||||||
|
:physical_address
|
||||||
|
])}
|
||||||
|
|
||||||
|
err ->
|
||||||
|
{:error, err}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets an event by it's URL
|
Gets an event by it's URL
|
||||||
"""
|
"""
|
||||||
|
@ -700,17 +721,28 @@ defmodule Mobilizon.Events do
|
||||||
[%Participant{}, ...]
|
[%Participant{}, ...]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def list_participants_for_event(uuid, page \\ nil, limit \\ nil) do
|
def list_participants_for_event(uuid, page \\ nil, limit \\ nil, include_not_improved \\ false)
|
||||||
Repo.all(
|
|
||||||
from(
|
def list_participants_for_event(uuid, page, limit, false) do
|
||||||
p in Participant,
|
query = do_list_participants_for_event(uuid, page, limit)
|
||||||
join: e in Event,
|
query = from(p in query, where: p.role != ^:not_approved)
|
||||||
on: p.event_id == e.id,
|
Repo.all(query)
|
||||||
where: e.uuid == ^uuid and p.role != ^:not_approved,
|
end
|
||||||
preload: [:actor]
|
|
||||||
)
|
def list_participants_for_event(uuid, page, limit, true) do
|
||||||
|> paginate(page, limit)
|
query = do_list_participants_for_event(uuid, page, limit)
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_list_participants_for_event(uuid, page, limit) do
|
||||||
|
from(
|
||||||
|
p in Participant,
|
||||||
|
join: e in Event,
|
||||||
|
on: p.event_id == e.id,
|
||||||
|
where: e.uuid == ^uuid,
|
||||||
|
preload: [:actor]
|
||||||
)
|
)
|
||||||
|
|> paginate(page, limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -787,6 +819,15 @@ defmodule Mobilizon.Events do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_participant_by_url(url) do
|
||||||
|
Repo.one(
|
||||||
|
from(p in Participant,
|
||||||
|
where: p.url == ^url,
|
||||||
|
preload: [:actor, :event]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a participant.
|
Creates a participant.
|
||||||
|
|
||||||
|
@ -800,9 +841,10 @@ defmodule Mobilizon.Events do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def create_participant(attrs \\ %{}) do
|
def create_participant(attrs \\ %{}) do
|
||||||
%Participant{}
|
with {:ok, %Participant{} = participant} <-
|
||||||
|> Participant.changeset(attrs)
|
%Participant{} |> Participant.changeset(attrs) |> Repo.insert() do
|
||||||
|> Repo.insert()
|
{:ok, Repo.preload(participant, [:event, :actor])}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -17,9 +17,10 @@ defmodule Mobilizon.Events.Participant do
|
||||||
alias Mobilizon.Events.{Participant, Event}
|
alias Mobilizon.Events.{Participant, Event}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
|
|
||||||
@primary_key false
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
schema "participants" do
|
schema "participants" do
|
||||||
field(:role, Mobilizon.Events.ParticipantRoleEnum, default: :participant)
|
field(:role, Mobilizon.Events.ParticipantRoleEnum, default: :participant)
|
||||||
|
field(:url, :string)
|
||||||
belongs_to(:event, Event, primary_key: true)
|
belongs_to(:event, Event, primary_key: true)
|
||||||
belongs_to(:actor, Actor, primary_key: true)
|
belongs_to(:actor, Actor, primary_key: true)
|
||||||
|
|
||||||
|
@ -29,7 +30,49 @@ defmodule Mobilizon.Events.Participant do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(%Participant{} = participant, attrs) do
|
def changeset(%Participant{} = participant, attrs) do
|
||||||
participant
|
participant
|
||||||
|> Ecto.Changeset.cast(attrs, [:role, :event_id, :actor_id])
|
|> Ecto.Changeset.cast(attrs, [:url, :role, :event_id, :actor_id])
|
||||||
|> validate_required([:role, :event_id, :actor_id])
|
|> generate_url()
|
||||||
|
|> validate_required([:url, :role, :event_id, :actor_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
# If there's a blank URL that's because we're doing the first insert
|
||||||
|
defp generate_url(%Ecto.Changeset{data: %Participant{url: nil}} = changeset) do
|
||||||
|
case fetch_change(changeset, :url) do
|
||||||
|
{:ok, _url} -> changeset
|
||||||
|
:error -> do_generate_url(changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Most time just go with the given URL
|
||||||
|
defp generate_url(%Ecto.Changeset{} = changeset), do: changeset
|
||||||
|
|
||||||
|
defp do_generate_url(%Ecto.Changeset{} = changeset) do
|
||||||
|
uuid = Ecto.UUID.generate()
|
||||||
|
|
||||||
|
changeset
|
||||||
|
|> put_change(
|
||||||
|
:url,
|
||||||
|
"#{MobilizonWeb.Endpoint.url()}/join/event/#{uuid}"
|
||||||
|
)
|
||||||
|
|> put_change(
|
||||||
|
:id,
|
||||||
|
uuid
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
We check that the actor asking to leave the event is not it's only organizer
|
||||||
|
We start by fetching the list of organizers and if there's only one of them
|
||||||
|
and that it's the actor requesting leaving the event we return true
|
||||||
|
"""
|
||||||
|
@spec check_that_participant_is_not_only_organizer(integer(), integer()) :: boolean()
|
||||||
|
def check_that_participant_is_not_only_organizer(event_id, actor_id) do
|
||||||
|
case Mobilizon.Events.list_organizers_participants_for_event(event_id) do
|
||||||
|
[%Participant{actor: %Actor{id: participant_actor_id}}] ->
|
||||||
|
participant_actor_id == actor_id
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,15 +2,12 @@ defmodule MobilizonWeb.API.Events do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
API for Events
|
API for Events
|
||||||
"""
|
"""
|
||||||
alias Mobilizon.Addresses
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
|
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
|
||||||
alias MobilizonWeb.API.Utils
|
alias MobilizonWeb.API.Utils
|
||||||
|
|
||||||
@visibility %{"PUBLIC" => :public, "PRIVATE" => :private}
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an event
|
Create an event
|
||||||
"""
|
"""
|
||||||
|
@ -51,15 +48,4 @@ defmodule MobilizonWeb.API.Events do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_physical_address(address_id) when is_number(address_id),
|
|
||||||
do: Addresses.get_address!(address_id)
|
|
||||||
|
|
||||||
defp get_physical_address(address_id) when is_binary(address_id) do
|
|
||||||
with {address_id, ""} <- Integer.parse(address_id) do
|
|
||||||
get_physical_address(address_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_physical_address(nil), do: nil
|
|
||||||
end
|
end
|
||||||
|
|
24
lib/mobilizon_web/api/participations.ex
Normal file
24
lib/mobilizon_web/api/participations.ex
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule MobilizonWeb.API.Participations do
|
||||||
|
@moduledoc """
|
||||||
|
Common API to join events and groups
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Events.{Event, Participant}
|
||||||
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@spec join(Event.t(), Actor.t()) :: {:ok, Participant.t()}
|
||||||
|
def join(%Event{id: event_id} = event, %Actor{id: actor_id} = actor) do
|
||||||
|
with {:error, :participant_not_found} <- Mobilizon.Events.get_participant(event_id, actor_id),
|
||||||
|
{:ok, activity, participant} <- ActivityPub.join(event, actor, true) do
|
||||||
|
{:ok, activity, participant}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def leave(%Event{} = event, %Actor{} = actor) do
|
||||||
|
with {:ok, activity, participant} <- ActivityPub.leave(event, actor, true) do
|
||||||
|
{:ok, activity, participant}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,7 +8,6 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Event, Participant}
|
alias Mobilizon.Events.{Event, Participant}
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Media.Picture
|
||||||
alias Mobilizon.Actors.Actor
|
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
alias MobilizonWeb.Resolvers.Person
|
alias MobilizonWeb.Resolvers.Person
|
||||||
|
|
||||||
|
@ -108,20 +107,18 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
}
|
}
|
||||||
) do
|
) do
|
||||||
with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
|
with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
|
||||||
{:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id),
|
{:has_event, {:ok, %Event{} = event}} <-
|
||||||
|
{:has_event, Mobilizon.Events.get_event_full(event_id)},
|
||||||
{:error, :participant_not_found} <- Mobilizon.Events.get_participant(event_id, actor_id),
|
{:error, :participant_not_found} <- Mobilizon.Events.get_participant(event_id, actor_id),
|
||||||
role <- Mobilizon.Events.get_default_participant_role(event),
|
{:ok, _activity, participant} <- MobilizonWeb.API.Participations.join(event, actor),
|
||||||
{:ok, participant} <-
|
|
||||||
Mobilizon.Events.create_participant(%{
|
|
||||||
role: role,
|
|
||||||
event_id: event.id,
|
|
||||||
actor_id: actor.id
|
|
||||||
}),
|
|
||||||
participant <-
|
participant <-
|
||||||
Map.put(participant, :event, event)
|
Map.put(participant, :event, event)
|
||||||
|> Map.put(:actor, Person.proxify_pictures(actor)) do
|
|> Map.put(:actor, Person.proxify_pictures(actor)) do
|
||||||
{:ok, participant}
|
{:ok, participant}
|
||||||
else
|
else
|
||||||
|
{:has_event, _} ->
|
||||||
|
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
|
||||||
|
|
||||||
{:is_owned, false} ->
|
{:is_owned, false} ->
|
||||||
{:error, "Actor id is not owned by authenticated user"}
|
{:error, "Actor id is not owned by authenticated user"}
|
||||||
|
|
||||||
|
@ -149,15 +146,15 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) do
|
) do
|
||||||
with {:is_owned, true, _} <- User.owns_actor(user, actor_id),
|
with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
|
||||||
{:ok, %Participant{} = participant} <-
|
{:has_event, {:ok, %Event{} = event}} <-
|
||||||
Mobilizon.Events.get_participant(event_id, actor_id),
|
{:has_event, Mobilizon.Events.get_event_full(event_id)},
|
||||||
{:only_organizer, false} <-
|
{:ok, _activity, _participant} <- MobilizonWeb.API.Participations.leave(event, actor) do
|
||||||
{:only_organizer, check_that_participant_is_not_only_organizer(event_id, actor_id)},
|
|
||||||
{:ok, _} <-
|
|
||||||
Mobilizon.Events.delete_participant(participant) do
|
|
||||||
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
|
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
|
||||||
else
|
else
|
||||||
|
{:has_event, _} ->
|
||||||
|
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
|
||||||
|
|
||||||
{:is_owned, false} ->
|
{:is_owned, false} ->
|
||||||
{:error, "Actor id is not owned by authenticated user"}
|
{:error, "Actor id is not owned by authenticated user"}
|
||||||
|
|
||||||
|
@ -173,20 +170,6 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
{:error, "You need to be logged-in to leave an event"}
|
{:error, "You need to be logged-in to leave an event"}
|
||||||
end
|
end
|
||||||
|
|
||||||
# We check that the actor asking to leave the event is not it's only organizer
|
|
||||||
# We start by fetching the list of organizers and if there's only one of them
|
|
||||||
# and that it's the actor requesting leaving the event we return true
|
|
||||||
@spec check_that_participant_is_not_only_organizer(integer(), integer()) :: boolean()
|
|
||||||
defp check_that_participant_is_not_only_organizer(event_id, actor_id) do
|
|
||||||
case Mobilizon.Events.list_organizers_participants_for_event(event_id) do
|
|
||||||
[%Participant{actor: %Actor{id: participant_actor_id}}] ->
|
|
||||||
participant_actor_id == actor_id
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an event
|
Create an event
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -11,18 +11,19 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Event, Comment}
|
alias Mobilizon.Events.{Event, Comment, Participant}
|
||||||
alias Mobilizon.Service.ActivityPub.Transmogrifier
|
alias Mobilizon.Service.ActivityPub.Transmogrifier
|
||||||
alias Mobilizon.Service.WebFinger
|
alias Mobilizon.Service.WebFinger
|
||||||
alias Mobilizon.Activity
|
alias Mobilizon.Activity
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.{Actor, Follower}
|
||||||
alias Mobilizon.Actors.Follower
|
|
||||||
|
|
||||||
alias Mobilizon.Service.Federator
|
alias Mobilizon.Service.Federator
|
||||||
alias Mobilizon.Service.HTTPSignatures.Signature
|
alias Mobilizon.Service.HTTPSignatures.Signature
|
||||||
|
|
||||||
|
alias Mobilizon.Service.ActivityPub.Convertible
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
import Mobilizon.Service.ActivityPub.Utils
|
import Mobilizon.Service.ActivityPub.Utils
|
||||||
import Mobilizon.Service.ActivityPub.Visibility
|
import Mobilizon.Service.ActivityPub.Visibility
|
||||||
|
@ -148,7 +149,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(%{to: to, actor: actor, object: object} = params, activity_follow_id \\ nil) do
|
def accept(%{to: to, actor: actor, object: object} = params, activity_wrapper_id \\ nil) do
|
||||||
# only accept false as false value
|
# only accept false as false value
|
||||||
local = !(params[:local] == false)
|
local = !(params[:local] == false)
|
||||||
|
|
||||||
|
@ -157,7 +158,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
"type" => "Accept",
|
"type" => "Accept",
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"object" => object,
|
"object" => object,
|
||||||
"id" => activity_follow_id || get_url(object) <> "/activity"
|
"id" => activity_wrapper_id || get_url(object) <> "/activity"
|
||||||
},
|
},
|
||||||
{:ok, activity, object} <- insert(data, local),
|
{:ok, activity, object} <- insert(data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
|
@ -165,11 +166,17 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject(%{to: to, actor: actor, object: object} = params) do
|
def reject(%{to: to, actor: actor, object: object} = params, activity_wrapper_id \\ nil) do
|
||||||
# only accept false as false value
|
# only accept false as false value
|
||||||
local = !(params[:local] == false)
|
local = !(params[:local] == false)
|
||||||
|
|
||||||
with data <- %{"to" => to, "type" => "Reject", "actor" => actor.url, "object" => object},
|
with data <- %{
|
||||||
|
"to" => to,
|
||||||
|
"type" => "Reject",
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => object,
|
||||||
|
"id" => activity_wrapper_id || get_url(object) <> "/activity"
|
||||||
|
},
|
||||||
{:ok, activity, object} <- insert(data, local),
|
{:ok, activity, object} <- insert(data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, object}
|
||||||
|
@ -383,6 +390,65 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def join(object, actor, local \\ true)
|
||||||
|
|
||||||
|
def join(%Event{} = event, %Actor{} = actor, local) do
|
||||||
|
with 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
|
||||||
|
}),
|
||||||
|
join_data <- Convertible.model_to_as(participant),
|
||||||
|
join_data <- Map.put(join_data, "to", [event.organizer_actor.url]),
|
||||||
|
join_data <- Map.put(join_data, "cc", []),
|
||||||
|
{:ok, activity, _} <- insert(join_data, local),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
if role === :participant do
|
||||||
|
accept(
|
||||||
|
%{to: [actor.url], actor: event.organizer_actor.url, object: join_data["id"]},
|
||||||
|
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{participant.id}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, activity, participant}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Implement me
|
||||||
|
def join(%Actor{type: :Group} = _group, %Actor{} = _actor, _local) do
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
|
||||||
|
def leave(object, actor, local \\ true)
|
||||||
|
|
||||||
|
# TODO: If we want to use this for exclusion we need to have an extra field for the actor that excluded the participant
|
||||||
|
def leave(
|
||||||
|
%Event{id: event_id, url: event_url} = event,
|
||||||
|
%Actor{id: actor_id, url: actor_url} = _actor,
|
||||||
|
local
|
||||||
|
) do
|
||||||
|
with {:only_organizer, false} <-
|
||||||
|
{:only_organizer,
|
||||||
|
Participant.check_that_participant_is_not_only_organizer(event_id, actor_id)},
|
||||||
|
{:ok, %Participant{} = participant} <-
|
||||||
|
Mobilizon.Events.get_participant(event_id, actor_id),
|
||||||
|
{:ok, %Participant{} = participant} <- Mobilizon.Events.delete_participant(participant),
|
||||||
|
leave_data <- %{
|
||||||
|
"type" => "Leave",
|
||||||
|
# If it's an exclusion it should be something else
|
||||||
|
"actor" => actor_url,
|
||||||
|
"object" => event_url,
|
||||||
|
"to" => [event.organizer_actor.url],
|
||||||
|
"cc" => []
|
||||||
|
},
|
||||||
|
{:ok, activity, _} <- insert(leave_data, local),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, activity, participant}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an actor locally by it's URL (AP ID)
|
Create an actor locally by it's URL (AP ID)
|
||||||
"""
|
"""
|
||||||
|
@ -482,7 +548,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
"""
|
"""
|
||||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
|
||||||
Logger.info("Federating #{id} to #{inbox}")
|
Logger.info("Federating #{id} to #{inbox}")
|
||||||
%URI{host: host, path: path} = URI.parse(inbox)
|
%URI{host: host, path: _path} = URI.parse(inbox)
|
||||||
|
|
||||||
digest = Signature.build_digest(json)
|
digest = Signature.build_digest(json)
|
||||||
date = Signature.generate_date_header()
|
date = Signature.generate_date_header()
|
||||||
|
|
|
@ -10,8 +10,6 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Comment do
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Service.ActivityPub.Converter
|
alias Mobilizon.Service.ActivityPub.Converter
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
|
||||||
alias MobilizonWeb.Endpoint
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@behaviour Converter
|
@behaviour Converter
|
||||||
|
|
29
lib/service/activity_pub/converters/participant.ex
Normal file
29
lib/service/activity_pub/converters/participant.ex
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
defmodule Mobilizon.Service.ActivityPub.Converters.Participant do
|
||||||
|
@moduledoc """
|
||||||
|
Flag converter
|
||||||
|
|
||||||
|
This module allows to convert reports from ActivityStream format to our own internal one, and back.
|
||||||
|
|
||||||
|
Note: Reports are named Flag in AS.
|
||||||
|
"""
|
||||||
|
alias Mobilizon.Events.Participant, as: ParticipantModel
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Convert an event struct to an ActivityStream representation
|
||||||
|
"""
|
||||||
|
@spec model_to_as(ParticipantModel.t()) :: map()
|
||||||
|
def model_to_as(%ParticipantModel{} = participant) do
|
||||||
|
%{
|
||||||
|
"type" => "Join",
|
||||||
|
"id" => participant.url,
|
||||||
|
"actor" => participant.actor.url,
|
||||||
|
"object" => participant.event.url
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defimpl Mobilizon.Service.ActivityPub.Convertible, for: Mobilizon.Events.Participant do
|
||||||
|
alias Mobilizon.Service.ActivityPub.Converters.Participant, as: ParticipantConverter
|
||||||
|
|
||||||
|
defdelegate model_to_as(event), to: ParticipantConverter
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.{Actor, Follower}
|
alias Mobilizon.Actors.{Actor, Follower}
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Event, Comment}
|
alias Mobilizon.Events.{Event, Comment, Participant}
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
alias Mobilizon.Service.ActivityPub.Visibility
|
alias Mobilizon.Service.ActivityPub.Visibility
|
||||||
|
@ -185,7 +185,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
|
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data
|
||||||
) do
|
) do
|
||||||
with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true),
|
with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true),
|
||||||
{:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower),
|
{:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower),
|
||||||
|
@ -198,61 +198,65 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO : Handle object being a Link
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{
|
||||||
"type" => "Accept",
|
"type" => "Accept",
|
||||||
"object" => follow_object,
|
"object" => accepted_object,
|
||||||
"actor" => _actor,
|
"actor" => _actor,
|
||||||
"id" => _id
|
"id" => id
|
||||||
} = data
|
} = data
|
||||||
) do
|
) do
|
||||||
with followed_actor_url <- get_actor(data),
|
with actor_url <- get_actor(data),
|
||||||
{:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed_actor_url),
|
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor_url),
|
||||||
{:ok, %Follower{approved: false, actor: follower, id: follow_id} = follow} <-
|
{:object_not_found, {:ok, activity, object}} <-
|
||||||
get_follow(follow_object),
|
{:object_not_found,
|
||||||
{:ok, activity, _} <-
|
do_handle_incoming_accept_following(accepted_object, actor) ||
|
||||||
ActivityPub.accept(
|
do_handle_incoming_accept_join(accepted_object, actor)} do
|
||||||
%{
|
{:ok, activity, object}
|
||||||
to: [follower.url],
|
|
||||||
actor: followed.url,
|
|
||||||
object: follow_object,
|
|
||||||
local: false
|
|
||||||
},
|
|
||||||
"#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}"
|
|
||||||
),
|
|
||||||
{:ok, %Follower{approved: true}} <- Actors.update_follower(follow, %{"approved" => true}) do
|
|
||||||
{:ok, activity, follow}
|
|
||||||
else
|
else
|
||||||
{:ok, %Follower{approved: true} = _follow} ->
|
{:object_not_found, nil} ->
|
||||||
{:error, "Follow already accepted"}
|
Logger.warn(
|
||||||
|
"Unable to process Accept activity #{inspect(id)}. Object #{inspect(accepted_object)} wasn't found."
|
||||||
|
)
|
||||||
|
|
||||||
|
:error
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.warn("Unable to process Accept Follow activity #{inspect(e)}")
|
Logger.warn(
|
||||||
|
"Unable to process Accept activity #{inspect(id)} for object #{inspect(accepted_object)} only returned #{
|
||||||
|
inspect(e)
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data
|
%{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data
|
||||||
) do
|
) do
|
||||||
with followed_actor_url <- get_actor(data),
|
with actor_url <- get_actor(data),
|
||||||
{:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed_actor_url),
|
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor_url),
|
||||||
{:ok, %Follower{approved: false, actor: follower, id: follow_id} = follow} <-
|
{:object_not_found, {:ok, activity, object}} <-
|
||||||
get_follow(follow_object),
|
{:object_not_found,
|
||||||
{:ok, activity, object} <-
|
do_handle_incoming_reject_following(rejected_object, actor) ||
|
||||||
ActivityPub.reject(%{
|
do_handle_incoming_reject_join(rejected_object, actor)} do
|
||||||
to: [follower.url],
|
|
||||||
type: "Reject",
|
|
||||||
actor: followed,
|
|
||||||
object: follow_object,
|
|
||||||
local: false
|
|
||||||
}),
|
|
||||||
{:ok, _follower} <- Actor.unfollow(followed, follower) do
|
|
||||||
{:ok, activity, object}
|
{:ok, activity, object}
|
||||||
else
|
else
|
||||||
|
{:object_not_found, nil} ->
|
||||||
|
Logger.warn(
|
||||||
|
"Unable to process Reject activity #{inspect(id)}. Object #{inspect(rejected_object)} wasn't found."
|
||||||
|
)
|
||||||
|
|
||||||
|
:error
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.debug(inspect(e))
|
Logger.warn(
|
||||||
|
"Unable to process Reject activity #{inspect(id)} for object #{inspect(rejected_object)} only returned #{
|
||||||
|
inspect(e)
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -272,7 +276,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
# end
|
# end
|
||||||
# #
|
# #
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
|
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
|
||||||
) do
|
) do
|
||||||
with actor <- get_actor(data),
|
with actor <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
|
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
|
||||||
|
@ -320,7 +324,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
"object" => object_id,
|
"object" => object_id,
|
||||||
"id" => cancelled_activity_id
|
"id" => cancelled_activity_id
|
||||||
},
|
},
|
||||||
"actor" => actor,
|
"actor" => _actor,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = data
|
} = data
|
||||||
) do
|
) do
|
||||||
|
@ -378,6 +382,43 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{"type" => "Join", "object" => object, "actor" => _actor, "id" => _id} = data
|
||||||
|
) do
|
||||||
|
with actor <- get_actor(data),
|
||||||
|
{:ok, %Actor{url: _actor_url} = actor} <- Actors.get_actor_by_url(actor),
|
||||||
|
{:ok, object} <- fetch_obj_helper(object),
|
||||||
|
{:ok, activity, object} <- ActivityPub.join(object, actor, false) do
|
||||||
|
{:ok, activity, object}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.debug(inspect(e))
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{"type" => "Leave", "object" => object, "actor" => actor, "id" => _id} = data
|
||||||
|
) do
|
||||||
|
with actor <- get_actor(data),
|
||||||
|
{:ok, %Actor{} = actor} <- Actors.get_actor_by_url(actor),
|
||||||
|
{:ok, object} <- fetch_obj_helper(object),
|
||||||
|
{:ok, activity, object} <- ActivityPub.leave(object, actor, false) do
|
||||||
|
{:ok, activity, object}
|
||||||
|
else
|
||||||
|
{:only_organizer, true} ->
|
||||||
|
Logger.warn(
|
||||||
|
"Actor #{inspect(actor)} tried to leave event #{inspect(object)} but it was the only organizer so we didn't detach it"
|
||||||
|
)
|
||||||
|
|
||||||
|
:error
|
||||||
|
|
||||||
|
e ->
|
||||||
|
Logger.error(inspect(e))
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# # TODO
|
# # TODO
|
||||||
# # Accept
|
# # Accept
|
||||||
|
@ -406,13 +447,187 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
{:error, :not_supported}
|
{:error, :not_supported}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Handle incoming `Accept` activities wrapping a `Follow` activity
|
||||||
|
"""
|
||||||
|
def do_handle_incoming_accept_following(follow_object, %Actor{} = actor) do
|
||||||
|
with {:follow,
|
||||||
|
{:ok,
|
||||||
|
%Follower{approved: false, actor: follower, id: follow_id, target_actor: followed} =
|
||||||
|
follow}} <-
|
||||||
|
{:follow, get_follow(follow_object)},
|
||||||
|
{:same_actor, true} <- {:same_actor, actor.id == followed.id},
|
||||||
|
{:ok, activity, _} <-
|
||||||
|
ActivityPub.accept(
|
||||||
|
%{
|
||||||
|
to: [follower.url],
|
||||||
|
actor: actor.url,
|
||||||
|
object: follow_object,
|
||||||
|
local: false
|
||||||
|
},
|
||||||
|
"#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}"
|
||||||
|
),
|
||||||
|
{:ok, %Follower{approved: true}} <- Actors.update_follower(follow, %{"approved" => true}) do
|
||||||
|
{:ok, activity, follow}
|
||||||
|
else
|
||||||
|
{:follow, _} ->
|
||||||
|
Logger.debug(
|
||||||
|
"Tried to handle an Accept activity but it's not containing a Follow activity"
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:same_actor} ->
|
||||||
|
{:error, "Actor who accepted the follow wasn't the target. Quite odd."}
|
||||||
|
|
||||||
|
{:ok, %Follower{approved: true} = _follow} ->
|
||||||
|
{:error, "Follow already accepted"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Handle incoming `Reject` activities wrapping a `Follow` activity
|
||||||
|
"""
|
||||||
|
def do_handle_incoming_reject_following(follow_object, %Actor{} = actor) do
|
||||||
|
with {:follow,
|
||||||
|
{:ok,
|
||||||
|
%Follower{approved: false, actor: follower, id: follow_id, target_actor: followed} =
|
||||||
|
follow}} <-
|
||||||
|
{:follow, get_follow(follow_object)},
|
||||||
|
{:same_actor, true} <- {:same_actor, actor.id == followed.id},
|
||||||
|
{:ok, activity, _} <-
|
||||||
|
ActivityPub.reject(
|
||||||
|
%{
|
||||||
|
to: [follower.url],
|
||||||
|
actor: actor.url,
|
||||||
|
object: follow_object,
|
||||||
|
local: false
|
||||||
|
},
|
||||||
|
"#{MobilizonWeb.Endpoint.url()}/reject/follow/#{follow_id}"
|
||||||
|
),
|
||||||
|
{:ok, %Follower{}} <- Actors.delete_follower(follow) do
|
||||||
|
{:ok, activity, follow}
|
||||||
|
else
|
||||||
|
{:follow, _} ->
|
||||||
|
Logger.debug(
|
||||||
|
"Tried to handle a Reject activity but it's not containing a Follow activity"
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:same_actor} ->
|
||||||
|
{:error, "Actor who rejected the follow wasn't the target. Quite odd."}
|
||||||
|
|
||||||
|
{:ok, %Follower{approved: true} = _follow} ->
|
||||||
|
{:error, "Follow already accepted"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Handle incoming `Accept` activities wrapping a `Join` activity on an event
|
||||||
|
"""
|
||||||
|
def do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
|
||||||
|
with {:join_event,
|
||||||
|
{:ok,
|
||||||
|
%Participant{role: :not_approved, actor: actor, id: join_id, event: event} =
|
||||||
|
participant}} <-
|
||||||
|
{:join_event, get_participant(join_object)},
|
||||||
|
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
|
||||||
|
# Or maybe for groups it's the group that sends the Accept activity
|
||||||
|
{:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
|
||||||
|
{:ok, activity, _} <-
|
||||||
|
ActivityPub.accept(
|
||||||
|
%{
|
||||||
|
to: [actor.url],
|
||||||
|
actor: actor_accepting.url,
|
||||||
|
object: join_object,
|
||||||
|
local: false
|
||||||
|
},
|
||||||
|
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{join_id}"
|
||||||
|
),
|
||||||
|
{:ok, %Participant{role: :participant}} <-
|
||||||
|
Events.update_participant(participant, %{"role" => :participant}) do
|
||||||
|
{:ok, activity, participant}
|
||||||
|
else
|
||||||
|
{:join_event, {:ok, %Participant{role: :participant}}} ->
|
||||||
|
Logger.debug(
|
||||||
|
"Tried to handle an Accept activity on a Join activity with a event object but the participant is already validated"
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:join_event, _err} ->
|
||||||
|
Logger.debug(
|
||||||
|
"Tried to handle an Accept activity but it's not containing a Join activity on a event"
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:same_actor} ->
|
||||||
|
{:error, "Actor who accepted the join wasn't the event organizer. Quite odd."}
|
||||||
|
|
||||||
|
{:ok, %Participant{role: :participant} = _follow} ->
|
||||||
|
{:error, "Participant"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Handle incoming `Reject` activities wrapping a `Join` activity on an event
|
||||||
|
"""
|
||||||
|
def do_handle_incoming_reject_join(join_object, %Actor{} = actor_accepting) do
|
||||||
|
with {:join_event,
|
||||||
|
{:ok,
|
||||||
|
%Participant{role: :not_approved, actor: actor, id: join_id, event: event} =
|
||||||
|
participant}} <-
|
||||||
|
{:join_event, get_participant(join_object)},
|
||||||
|
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
|
||||||
|
# Or maybe for groups it's the group that sends the Accept activity
|
||||||
|
{:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
|
||||||
|
{:ok, activity, _} <-
|
||||||
|
ActivityPub.reject(
|
||||||
|
%{
|
||||||
|
to: [actor.url],
|
||||||
|
actor: actor_accepting.url,
|
||||||
|
object: join_object,
|
||||||
|
local: false
|
||||||
|
},
|
||||||
|
"#{MobilizonWeb.Endpoint.url()}/reject/join/#{join_id}"
|
||||||
|
),
|
||||||
|
{:ok, %Participant{}} <-
|
||||||
|
Events.delete_participant(participant) do
|
||||||
|
{:ok, activity, participant}
|
||||||
|
else
|
||||||
|
{:join_event, {:ok, %Participant{role: :participant}}} ->
|
||||||
|
Logger.debug(
|
||||||
|
"Tried to handle an Reject activity on a Join activity with a event object but the participant is already validated"
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:join_event, _err} ->
|
||||||
|
Logger.debug(
|
||||||
|
"Tried to handle an Reject activity but it's not containing a Join activity on a event"
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:same_actor} ->
|
||||||
|
{:error, "Actor who rejected the join wasn't the event organizer. Quite odd."}
|
||||||
|
|
||||||
|
{:ok, %Participant{role: :participant} = _follow} ->
|
||||||
|
{:error, "Participant"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Add do_handle_incoming_accept_join/1 on Groups
|
||||||
|
|
||||||
defp get_follow(follow_object) do
|
defp get_follow(follow_object) do
|
||||||
with follow_object_id when not is_nil(follow_object_id) <- Utils.get_url(follow_object),
|
with follow_object_id when not is_nil(follow_object_id) <- Utils.get_url(follow_object),
|
||||||
{:not_found, %Follower{} = follow} <-
|
{:not_found, %Follower{} = follow} <-
|
||||||
{:not_found, Actors.get_follow_by_url(follow_object_id)} do
|
{:not_found, Actors.get_follow_by_url(follow_object_id)} do
|
||||||
{:ok, follow}
|
{:ok, follow}
|
||||||
else
|
else
|
||||||
{:not_found, err} ->
|
{:not_found, _err} ->
|
||||||
{:error, "Follow URL not found"}
|
{:error, "Follow URL not found"}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -420,6 +635,20 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_participant(join_object) do
|
||||||
|
with join_object_id when not is_nil(join_object_id) <- Utils.get_url(join_object),
|
||||||
|
{:not_found, %Participant{} = participant} <-
|
||||||
|
{:not_found, Events.get_participant_by_url(join_object_id)} do
|
||||||
|
{:ok, participant}
|
||||||
|
else
|
||||||
|
{:not_found, _err} ->
|
||||||
|
{:error, "Participant URL not found"}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "ActivityPub ID not found in Accept Join object"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) do
|
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) do
|
||||||
with false <- String.starts_with?(in_reply_to, "http"),
|
with false <- String.starts_with?(in_reply_to, "http"),
|
||||||
{:ok, replied_to_object} <- fetch_obj_helper(in_reply_to) do
|
{:ok, replied_to_object} <- fetch_obj_helper(in_reply_to) do
|
||||||
|
|
|
@ -512,7 +512,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
object_actor_url,
|
object_actor_url,
|
||||||
object_url,
|
object_url,
|
||||||
activity_id,
|
activity_id,
|
||||||
public \\ true
|
public
|
||||||
) do
|
) do
|
||||||
{to, cc} =
|
{to, cc} =
|
||||||
if public do
|
if public do
|
||||||
|
@ -611,6 +611,24 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
|> Map.merge(additional)
|
|> Map.merge(additional)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def make_join_data(%Event{} = event, %Actor{} = actor) do
|
||||||
|
%{
|
||||||
|
"type" => "Join",
|
||||||
|
"id" => "#{actor.url}/join/event/id",
|
||||||
|
"actor" => actor.url,
|
||||||
|
"object" => event.url
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_join_data(%Actor{type: :Group} = event, %Actor{} = actor) do
|
||||||
|
%{
|
||||||
|
"type" => "Join",
|
||||||
|
"id" => "#{actor.url}/join/group/id",
|
||||||
|
"actor" => actor.url,
|
||||||
|
"object" => event.url
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Converts PEM encoded keys to a public key representation
|
Converts PEM encoded keys to a public key representation
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Mobilizon.Service.ActivityPub.Visibility do
|
||||||
Utility functions related to content visibility
|
Utility functions related to content visibility
|
||||||
"""
|
"""
|
||||||
alias Mobilizon.Activity
|
alias Mobilizon.Activity
|
||||||
alias Mobilizon.Events.Event
|
|
||||||
|
|
||||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule Mobilizon.Repo.Migrations.AddUrlAndUuidToParticipants do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
drop(index(:participants, :event_id))
|
||||||
|
drop_if_exists(index(:participants, :account_id))
|
||||||
|
drop_if_exists(index(:participants, :actor_id))
|
||||||
|
drop(constraint(:participants, "participants_event_id_fkey"))
|
||||||
|
|
||||||
|
# This is because even though we renamed the table accounts to actors indexes kept this name
|
||||||
|
drop_if_exists(constraint(:participants, "participants_account_id_fkey"))
|
||||||
|
drop_if_exists(constraint(:participants, "participants_actor_id_fkey"))
|
||||||
|
drop(constraint(:participants, "participants_pkey"))
|
||||||
|
|
||||||
|
alter table(:participants, primary_key: false) do
|
||||||
|
modify(:event_id, references(:events, on_delete: :delete_all), primary_key: false)
|
||||||
|
modify(:actor_id, references(:actors, on_delete: :delete_all), primary_key: false)
|
||||||
|
add(:id, :uuid, primary_key: true)
|
||||||
|
add(:url, :string, null: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:participants, :event_id))
|
||||||
|
create(index(:participants, :actor_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
drop(index(:participants, :event_id))
|
||||||
|
drop(index(:participants, :actor_id))
|
||||||
|
drop(constraint(:participants, "participants_event_id_fkey"))
|
||||||
|
drop(constraint(:participants, "participants_actor_id_fkey"))
|
||||||
|
drop(constraint(:participants, "participants_pkey"))
|
||||||
|
|
||||||
|
alter table(:participants, primary_key: false) do
|
||||||
|
modify(:event_id, references(:events, on_delete: :nothing), primary_key: true)
|
||||||
|
modify(:actor_id, references(:actors, on_delete: :nothing), primary_key: true)
|
||||||
|
remove(:id)
|
||||||
|
remove(:url)
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:participants, :event_id))
|
||||||
|
create(index(:participants, :actor_id))
|
||||||
|
end
|
||||||
|
end
|
22
test/fixtures/mobilizon-join-activity.json
vendored
Normal file
22
test/fixtures/mobilizon-join-activity.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"type": "Join",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "Kn1/UkAQGJVaXBfWLAHcnwHg8YMAUqlEaBuYLazAG+pz5hqivsyrBmPV186Xzr+B4ZLExA9+SnOoNx/GOz4hBm0kAmukNSILAsUd84tcJ2yT9zc1RKtembK4WiwOw7li0+maeDN0HaB6t+6eTqsCWmtiZpprhXD8V1GGT8yG7X24fQ9oFGn+ng7lasbcCC0988Y1eGqNe7KryxcPuQz57YkDapvtONzk8gyLTkZMV4De93MyRHq6GVjQVIgtiYabQAxrX6Q8C+4P/jQoqdWJHEe+MY5JKyNaT/hMPt2Md1ok9fZQBGHlErk22/zy8bSN19GdG09HmIysBUHRYpBLig==",
|
||||||
|
"creator": "http://mobilizon.test/users/tcit#main-key",
|
||||||
|
"created": "2018-02-17T13:29:31Z"
|
||||||
|
},
|
||||||
|
"object": "http://mobilizon.test/events/some-uuid",
|
||||||
|
"id": "http://mobilizon2.test/@admin/join/event/1",
|
||||||
|
"actor": "http://mobilizon2.test/@admin",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"Hashtag": "as:Hashtag"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
22
test/fixtures/mobilizon-leave-activity.json
vendored
Normal file
22
test/fixtures/mobilizon-leave-activity.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"type": "Leave",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "Kn1/UkAQGJVaXBfWLAHcnwHg8YMAUqlEaBuYLazAG+pz5hqivsyrBmPV186Xzr+B4ZLExA9+SnOoNx/GOz4hBm0kAmukNSILAsUd84tcJ2yT9zc1RKtembK4WiwOw7li0+maeDN0HaB6t+6eTqsCWmtiZpprhXD8V1GGT8yG7X24fQ9oFGn+ng7lasbcCC0988Y1eGqNe7KryxcPuQz57YkDapvtONzk8gyLTkZMV4De93MyRHq6GVjQVIgtiYabQAxrX6Q8C+4P/jQoqdWJHEe+MY5JKyNaT/hMPt2Md1ok9fZQBGHlErk22/zy8bSN19GdG09HmIysBUHRYpBLig==",
|
||||||
|
"creator": "http://mobilizon.test/users/tcit#main-key",
|
||||||
|
"created": "2018-02-17T13:29:31Z"
|
||||||
|
},
|
||||||
|
"object": "http://mobilizon.test/events/some-uuid",
|
||||||
|
"id": "http://mobilizon2.test/@admin/join/event/1",
|
||||||
|
"actor": "http://mobilizon2.test/@admin",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"Hashtag": "as:Hashtag"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Comment, Event}
|
alias Mobilizon.Events.{Comment, Event, Participant}
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
alias Mobilizon.Service.ActivityPub.Transmogrifier
|
alias Mobilizon.Service.ActivityPub.Transmogrifier
|
||||||
|
@ -695,6 +695,120 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
assert activity.data["actor"] == reporter_url
|
assert activity.data["actor"] == reporter_url
|
||||||
assert activity.data["cc"] == [reported_url]
|
assert activity.data["cc"] == [reported_url]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it accepts Join activities" do
|
||||||
|
%Actor{url: _organizer_url} = organizer = insert(:actor)
|
||||||
|
%Actor{url: participant_url} = _participant = insert(:actor)
|
||||||
|
|
||||||
|
%Event{url: event_url} = _event = insert(:event, organizer_actor: organizer)
|
||||||
|
|
||||||
|
join_data =
|
||||||
|
File.read!("test/fixtures/mobilizon-join-activity.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.put("actor", participant_url)
|
||||||
|
|> Map.put("object", event_url)
|
||||||
|
|
||||||
|
assert {:ok, activity, _} = Transmogrifier.handle_incoming(join_data)
|
||||||
|
|
||||||
|
assert activity.data["object"] == event_url
|
||||||
|
assert activity.data["actor"] == participant_url
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it accepts Accept activities for Join activities" do
|
||||||
|
%Actor{url: organizer_url} = organizer = insert(:actor)
|
||||||
|
%Actor{} = participant_actor = insert(:actor)
|
||||||
|
|
||||||
|
%Event{} = event = insert(:event, organizer_actor: organizer, join_options: :restricted)
|
||||||
|
|
||||||
|
{:ok, join_activity, participation} = ActivityPub.join(event, participant_actor)
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.put("actor", organizer_url)
|
||||||
|
|> Map.put("object", participation.url)
|
||||||
|
|
||||||
|
{:ok, accept_activity, _} = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
assert accept_activity.data["object"] == join_activity.data["id"]
|
||||||
|
assert accept_activity.data["object"] =~ "/join/"
|
||||||
|
assert accept_activity.data["id"] =~ "/accept/join/"
|
||||||
|
|
||||||
|
# We don't accept already accepted Accept activities
|
||||||
|
:error = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it accepts Reject activities for Join activities" do
|
||||||
|
%Actor{url: organizer_url} = organizer = insert(:actor)
|
||||||
|
%Actor{} = participant_actor = insert(:actor)
|
||||||
|
|
||||||
|
%Event{} = event = insert(:event, organizer_actor: organizer, join_options: :restricted)
|
||||||
|
|
||||||
|
{:ok, join_activity, participation} = ActivityPub.join(event, participant_actor)
|
||||||
|
|
||||||
|
reject_data =
|
||||||
|
File.read!("test/fixtures/mastodon-reject-activity.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.put("actor", organizer_url)
|
||||||
|
|> Map.put("object", participation.url)
|
||||||
|
|
||||||
|
{:ok, reject_activity, _} = Transmogrifier.handle_incoming(reject_data)
|
||||||
|
assert reject_activity.data["object"] == join_activity.data["id"]
|
||||||
|
assert reject_activity.data["object"] =~ "/join/"
|
||||||
|
assert reject_activity.data["id"] =~ "/reject/join/"
|
||||||
|
|
||||||
|
# We don't accept already rejected Reject activities
|
||||||
|
assert :error == Transmogrifier.handle_incoming(reject_data)
|
||||||
|
|
||||||
|
# Organiser is not present since we use factories directly
|
||||||
|
assert Events.list_participants_for_event(event.uuid, 1, 10, true) |> Enum.map(& &1.id) ==
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it accepts Leave activities" do
|
||||||
|
%Actor{url: _organizer_url} = organizer = insert(:actor)
|
||||||
|
%Actor{url: participant_url} = participant_actor = insert(:actor)
|
||||||
|
|
||||||
|
%Event{url: event_url} =
|
||||||
|
event = insert(:event, organizer_actor: organizer, join_options: :restricted)
|
||||||
|
|
||||||
|
organizer_participation =
|
||||||
|
%Participant{} = insert(:participant, event: event, actor: organizer, role: :creator)
|
||||||
|
|
||||||
|
{:ok, _join_activity, _participation} = ActivityPub.join(event, participant_actor)
|
||||||
|
|
||||||
|
join_data =
|
||||||
|
File.read!("test/fixtures/mobilizon-leave-activity.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.put("actor", participant_url)
|
||||||
|
|> Map.put("object", event_url)
|
||||||
|
|
||||||
|
assert {:ok, activity, _} = Transmogrifier.handle_incoming(join_data)
|
||||||
|
|
||||||
|
assert activity.data["object"] == event_url
|
||||||
|
assert activity.data["actor"] == participant_url
|
||||||
|
|
||||||
|
# The only participant left is the organizer
|
||||||
|
assert Events.list_participants_for_event(event.uuid, 1, 10, true) |> Enum.map(& &1.id) == [
|
||||||
|
organizer_participation.id
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it refuses Leave activities when actor is the only organizer" do
|
||||||
|
%Actor{url: organizer_url} = organizer = insert(:actor)
|
||||||
|
|
||||||
|
%Event{url: event_url} =
|
||||||
|
event = insert(:event, organizer_actor: organizer, join_options: :restricted)
|
||||||
|
|
||||||
|
%Participant{} = insert(:participant, event: event, actor: organizer, role: :creator)
|
||||||
|
|
||||||
|
join_data =
|
||||||
|
File.read!("test/fixtures/mobilizon-leave-activity.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.put("actor", organizer_url)
|
||||||
|
|> Map.put("object", event_url)
|
||||||
|
|
||||||
|
assert :error = Transmogrifier.handle_incoming(join_data)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "prepare outgoing" do
|
describe "prepare outgoing" do
|
||||||
|
|
|
@ -116,7 +116,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
|> auth_conn(user)
|
|> auth_conn(user)
|
||||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||||
|
|
||||||
assert hd(json_response(res, 200)["errors"])["message"] =~ "Event id not found"
|
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||||
|
"Event with this ID 1042 doesn't exist"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "actor_leave_event/3 should delete a participant from an event", %{
|
test "actor_leave_event/3 should delete a participant from an event", %{
|
||||||
|
@ -290,13 +291,14 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
user: user,
|
user: user,
|
||||||
actor: actor
|
actor: actor
|
||||||
} do
|
} do
|
||||||
|
event = insert(:event)
|
||||||
participant = insert(:participant, %{actor: actor})
|
participant = insert(:participant, %{actor: actor})
|
||||||
|
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation {
|
mutation {
|
||||||
leaveEvent(
|
leaveEvent(
|
||||||
actor_id: #{participant.actor.id},
|
actor_id: #{participant.actor.id},
|
||||||
event_id: 1042
|
event_id: #{event.id}
|
||||||
) {
|
) {
|
||||||
actor {
|
actor {
|
||||||
id
|
id
|
||||||
|
@ -355,13 +357,13 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["participants"] == [
|
assert json_response(res, 200)["data"]["participants"] == [
|
||||||
%{
|
|
||||||
"actor" => %{"preferredUsername" => context.actor.preferred_username},
|
|
||||||
"role" => "creator"
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
"actor" => %{"preferredUsername" => participant2.actor.preferred_username},
|
"actor" => %{"preferredUsername" => participant2.actor.preferred_username},
|
||||||
"role" => "participant"
|
"role" => "participant"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"actor" => %{"preferredUsername" => context.actor.preferred_username},
|
||||||
|
"role" => "creator"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -124,15 +124,20 @@ defmodule Mobilizon.Factory do
|
||||||
tags: build_list(3, :tag),
|
tags: build_list(3, :tag),
|
||||||
url: Routes.page_url(Endpoint, :event, uuid),
|
url: Routes.page_url(Endpoint, :event, uuid),
|
||||||
picture: insert(:picture),
|
picture: insert(:picture),
|
||||||
uuid: uuid
|
uuid: uuid,
|
||||||
|
join_options: :free
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def participant_factory do
|
def participant_factory do
|
||||||
|
uuid = Ecto.UUID.generate()
|
||||||
|
|
||||||
%Mobilizon.Events.Participant{
|
%Mobilizon.Events.Participant{
|
||||||
event: build(:event),
|
event: build(:event),
|
||||||
actor: build(:actor),
|
actor: build(:actor),
|
||||||
role: :creator
|
role: :creator,
|
||||||
|
url: "#{Endpoint.url()}/join/event/#{uuid}",
|
||||||
|
id: uuid
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue