transmogrifier: gracefully ignore duplicated object deletes

The object lookup is later repeated in the validator, but due to
caching shouldn't incur any noticeable performance impact.
It’s actually preferable to check here, since it avoids the otherwise
occuring user lookup and overhead from starting and aborting a
transaction
This commit is contained in:
Oneric 2024-12-18 01:34:33 +01:00
parent d7ab4d9b0b
commit 14bda007ae
2 changed files with 19 additions and 4 deletions

View file

@ -216,6 +216,11 @@ def get_cached_by_ap_id(ap_id) do
end
end
# Intentionally accepts non-Object arguments!
@spec is_tombstone_object?(term()) :: boolean()
def is_tombstone_object?(%Object{data: %{"type" => "Tombstone"}}), do: true
def is_tombstone_object?(_), do: false
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
%ObjectTombstone{
id: id,

View file

@ -556,19 +556,29 @@ defp handle_incoming_normalised(
%{"type" => "Delete"} = data,
_options
) do
with {:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
oid_result = ObjectValidators.ObjectID.cast(data["object"])
with {_, {:ok, object_id}} <- {:object_id, oid_result},
object <- Object.get_cached_by_ap_id(object_id),
{_, false} <- {:tombstone, Object.is_tombstone_object?(object) && !data["actor"]},
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
{:object_id, _} ->
{:error, {:validate, "Invalid object id: #{data["object"]}"}}
{:tombstone, true} ->
{:error, :ignore}
{:error, {:validate, {:error, %Ecto.Changeset{errors: errors}}}} = e ->
if errors[:object] == {"can't find object", []} do
# Check if we have a create activity for this
# (e.g. from a db prune without --prune-activities)
# We'd still like to process side effects so insert a tombstone and retry
# We'd still like to process side effects so insert a fake tombstone and retry
# (real tombstones from Object.delete do not have an actor field)
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-
Activity.create_by_object_ap_id(object_id) |> Repo.one(),
# We have one, insert a tombstone and retry
{:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id),
{:ok, _tombstone} <- Object.create(tombstone_data) do
handle_incoming(data)