mobilizon/lib/federation/activity_pub/types/resources.ex

216 lines
7.0 KiB
Elixir

defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
@moduledoc false
alias Mobilizon.{Actors, Resources}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Permission
alias Mobilizon.Federation.ActivityPub.Types.Entity
alias alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Resources.Resource
alias Mobilizon.Service.Activity.Resource, as: ResourceActivity
alias Mobilizon.Service.RichMedia.Parser
require Logger
import Mobilizon.Federation.ActivityPub.Utils,
only: [make_create_data: 2, make_update_data: 2, make_add_data: 3, make_move_data: 4]
@behaviour Entity
@impl Entity
@spec create(map(), map()) ::
{:ok, Resource.t(), ActivityStream.t()}
| {:error, Ecto.Changeset.t() | :creator_not_found | :group_not_found}
def create(%{type: type} = args, additional) do
args =
case type do
:folder ->
args
_ ->
case Parser.parse(Map.get(args, :resource_url)) do
{:ok, metadata} ->
Map.put(args, :metadata, metadata)
_ ->
args
end
end
with {:ok,
%Resource{actor_id: group_id, creator_id: creator_id, parent_id: parent_id} = resource} <-
Resources.create_resource(args),
{:ok, %Actor{} = group, %Actor{url: creator_url} = creator} <-
group_and_creator(group_id, creator_id) do
ResourceActivity.insert_activity(resource, subject: "resource_created")
resource_as_data = Convertible.model_to_as(%{resource | actor: group, creator: creator})
audience = %{
"to" => [group.members_url],
"cc" => [],
"actor" => creator_url,
"attributedTo" => [creator_url]
}
create_data =
case parent_id do
nil ->
make_create_data(resource_as_data, Map.merge(audience, additional))
parent_id ->
# In case the resource has a parent we don't `Create` the resource but `Add` it to an existing resource
parent = Resources.get_resource(parent_id)
make_add_data(resource_as_data, parent, Map.merge(audience, additional))
end
{:ok, resource, create_data}
end
end
@impl Entity
@spec update(Resource.t(), map(), map()) ::
{:ok, Resource.t(), ActivityStream.t()}
| {:error, Ecto.Changeset.t() | :creator_not_found | :group_not_found}
def update(
%Resource{parent_id: old_parent_id} = old_resource,
%{parent_id: parent_id} = args,
additional
)
when old_parent_id != parent_id do
move(old_resource, args, additional)
end
# Simple rename
def update(%Resource{} = old_resource, %{title: title} = _args, additional) do
with {:ok, %Resource{actor_id: group_id, creator_id: creator_id} = resource} <-
Resources.update_resource(old_resource, %{title: title}),
{:ok, %Actor{} = group, %Actor{url: creator_url}} <-
group_and_creator(group_id, creator_id) do
ResourceActivity.insert_activity(resource,
subject: "resource_renamed",
old_resource: old_resource
)
resource_as_data = Convertible.model_to_as(%{resource | actor: group})
audience = %{
"to" => [group.members_url],
"cc" => [],
"actor" => creator_url,
"attributedTo" => [creator_url]
}
update_data = make_update_data(resource_as_data, Map.merge(audience, additional))
{:ok, resource, update_data}
end
end
@spec move(Resource.t(), map(), map()) ::
{:ok, Resource.t(), ActivityStream.t()}
| {:error,
Ecto.Changeset.t()
| :creator_not_found
| :group_not_found
| :new_parent_not_found
| :old_parent_not_found}
def move(
%Resource{parent_id: old_parent_id} = old_resource,
%{parent_id: _new_parent_id} = args,
additional
) do
with {:ok,
%Resource{actor_id: group_id, creator_id: creator_id, parent_id: new_parent_id} =
resource} <-
Resources.update_resource(old_resource, args),
{:ok, old_parent, new_parent} <- parents(old_parent_id, new_parent_id),
{:ok, %Actor{} = group, %Actor{url: creator_url}} <-
group_and_creator(group_id, creator_id) do
ResourceActivity.insert_activity(resource, subject: "resource_moved")
resource_as_data = Convertible.model_to_as(%{resource | actor: group})
audience = %{
"to" => [group.members_url],
"cc" => [],
"actor" => creator_url,
"attributedTo" => [creator_url]
}
move_data =
make_move_data(
resource_as_data,
old_parent,
new_parent,
Map.merge(audience, additional)
)
{:ok, resource, move_data}
end
end
@impl Entity
@spec delete(Resource.t(), Actor.t(), boolean, map()) ::
{:ok, ActivityStream.t(), Actor.t(), Resource.t()} | {:error, Ecto.Changeset.t()}
def delete(
%Resource{url: url, actor: %Actor{url: group_url, members_url: members_url}} = resource,
%Actor{url: actor_url} = actor,
_local,
_additionnal
) do
Logger.debug("Building Delete Resource activity")
activity_data = %{
"actor" => actor_url,
"attributedTo" => [group_url],
"type" => "Delete",
"object" => Convertible.model_to_as(resource),
"id" => url <> "/delete",
"to" => [members_url]
}
case Resources.delete_resource(resource) do
{:ok, _resource} ->
ResourceActivity.insert_activity(resource, subject: "resource_deleted")
Cachex.del(:activity_pub, "resource_#{resource.id}")
{:ok, activity_data, actor, resource}
{:error, %Ecto.Changeset{} = err} ->
{:error, err}
end
end
@spec actor(Todo.t()) :: Actor.t() | nil
def actor(%Resource{creator_id: creator_id}),
do: Actors.get_actor(creator_id)
@spec group_actor(Todo.t()) :: Actor.t() | nil
def group_actor(%Resource{actor_id: actor_id}), do: Actors.get_actor(actor_id)
@spec permissions(TodoList.t()) :: Permission.t()
def permissions(%Resource{}) do
%Permission{access: :member, create: :member, update: :member, delete: :member}
end
@spec group_and_creator(integer(), integer()) ::
{:ok, Actor.t(), Actor.t()} | {:error, :creator_not_found | :group_not_found}
defp group_and_creator(group_id, creator_id) do
case Actors.get_group_by_actor_id(group_id) do
{:ok, %Actor{} = group} ->
case Actors.get_actor(creator_id) do
%Actor{} = creator ->
{:ok, group, creator}
nil ->
{:error, :creator_not_found}
end
{:error, :group_not_found} ->
{:error, :group_not_found}
end
end
@spec parents(String.t(), String.t()) ::
{:ok, Resource.t(), Resource.t()}
defp parents(old_parent_id, new_parent_id) do
{:ok, Resources.get_resource(old_parent_id), Resources.get_resource(new_parent_id)}
end
end