Unify the logic of updating objects

This commit is contained in:
Tusooa Zhu 2022-06-25 00:32:22 -04:00
parent e0d6da4e7d
commit 99a6f50316
No known key found for this signature in database
GPG key ID: 7B467EDE43A08224
6 changed files with 183 additions and 101 deletions

View file

@ -425,46 +425,4 @@ defmodule Pleroma.Object do
end end
def object_data_hashtags(_), do: [] def object_data_hashtags(_), do: []
def history_for(object) do
with history <- Map.get(object, "formerRepresentations"),
true <- is_map(history),
"OrderedCollection" <- Map.get(history, "type"),
true <- is_list(Map.get(history, "orderedItems")),
true <- is_integer(Map.get(history, "totalItems")) do
history
else
_ -> history_skeleton()
end
end
defp history_skeleton do
%{
"type" => "OrderedCollection",
"totalItems" => 0,
"orderedItems" => []
}
end
def maybe_update_history(updated_object, orig_object_data, updated) do
if not updated do
updated_object
else
# Put edit history
# Note that we may have got the edit history by first fetching the object
history = Object.history_for(orig_object_data)
latest_history_item =
orig_object_data
|> Map.drop(["id", "formerRepresentations"])
new_history =
history
|> Map.put("orderedItems", [latest_history_item | history["orderedItems"]])
|> Map.put("totalItems", history["totalItems"] + 1)
updated_object
|> Map.put("formerRepresentations", new_history)
end
end
end end

View file

@ -50,7 +50,14 @@ defmodule Pleroma.Object.Fetcher do
Pleroma.Constants.status_updatable_fields() Pleroma.Constants.status_updatable_fields()
|> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end) |> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end)
new_data |> Object.maybe_update_history(old_data, changed?) %{updated_object: updated_object} =
new_data
|> Object.Updater.maybe_update_history(old_data,
updated: changed?,
use_history_in_new_object?: false
)
updated_object
else else
new_data new_data
end end

View file

@ -0,0 +1,157 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object.Updater do
require Pleroma.Constants
def update_content_fields(orig_object_data, updated_object) do
Pleroma.Constants.status_updatable_fields()
|> Enum.reduce(
%{data: orig_object_data, updated: false},
fn field, %{data: data, updated: updated} ->
updated = updated or Map.get(updated_object, field) != Map.get(orig_object_data, field)
data =
if Map.has_key?(updated_object, field) do
Map.put(data, field, updated_object[field])
else
Map.drop(data, [field])
end
%{data: data, updated: updated}
end
)
end
def maybe_history(object) do
with history <- Map.get(object, "formerRepresentations"),
true <- is_map(history),
"OrderedCollection" <- Map.get(history, "type"),
true <- is_list(Map.get(history, "orderedItems")),
true <- is_integer(Map.get(history, "totalItems")) do
history
else
_ -> nil
end
end
def history_for(object) do
with history when not is_nil(history) <- maybe_history(object) do
history
else
_ -> history_skeleton()
end
end
defp history_skeleton do
%{
"type" => "OrderedCollection",
"totalItems" => 0,
"orderedItems" => []
}
end
def maybe_update_history(
updated_object,
orig_object_data,
opts
) do
updated = opts[:updated]
use_history_in_new_object? = opts[:use_history_in_new_object?]
if not updated do
%{updated_object: updated_object, used_history_in_new_object?: false}
else
# Put edit history
# Note that we may have got the edit history by first fetching the object
{new_history, used_history_in_new_object?} =
with true <- use_history_in_new_object?,
updated_history when not is_nil(updated_history) <- maybe_history(updated_object) do
{updated_history, true}
else
_ ->
history = history_for(orig_object_data)
latest_history_item =
orig_object_data
|> Map.drop(["id", "formerRepresentations"])
updated_history =
history
|> Map.put("orderedItems", [latest_history_item | history["orderedItems"]])
|> Map.put("totalItems", history["totalItems"] + 1)
{updated_history, false}
end
updated_object =
updated_object
|> Map.put("formerRepresentations", new_history)
%{updated_object: updated_object, used_history_in_new_object?: used_history_in_new_object?}
end
end
defp maybe_update_poll(to_be_updated, updated_object) do
choice_key = fn data ->
if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf"
end
with true <- to_be_updated["type"] == "Question",
key <- choice_key.(updated_object),
true <- key == choice_key.(to_be_updated),
orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])),
new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])),
true <- orig_choices == new_choices do
# Choices are the same, but counts are different
to_be_updated
|> Map.put(key, updated_object[key])
else
# Choices (or vote type) have changed, do not allow this
_ -> to_be_updated
end
end
# This calculates the data to be sent as the object of an Update.
# new_data's formerRepresentations is not considered.
# formerRepresentations is added to the returned data.
def make_update_object_data(original_data, new_data, date) do
%{data: updated_data, updated: updated} =
original_data
|> update_content_fields(new_data)
if not updated do
updated_data
else
%{updated_object: updated_data} =
updated_data
|> maybe_update_history(original_data, updated: updated, use_history_in_new_object?: false)
updated_data
|> Map.put("updated", date)
end
end
# This calculates the data of the new Object from an Update.
# new_data's formerRepresentations is considered.
def make_new_object_data_from_update_object(original_data, new_data) do
%{data: updated_data, updated: updated} =
original_data
|> update_content_fields(new_data)
%{updated_object: updated_data, used_history_in_new_object?: used_history_in_new_object?} =
updated_data
|> maybe_update_history(original_data, updated: updated, use_history_in_new_object?: false)
updated_data =
updated_data
|> maybe_update_poll(new_data)
%{
updated_data: updated_data,
updated: updated,
used_history_in_new_object?: used_history_in_new_object?
}
end
end

View file

@ -412,45 +412,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end end
@updatable_object_types ["Note", "Question"] @updatable_object_types ["Note", "Question"]
defp update_content_fields(orig_object_data, updated_object) do
Pleroma.Constants.status_updatable_fields()
|> Enum.reduce(
%{data: orig_object_data, updated: false},
fn field, %{data: data, updated: updated} ->
updated = updated or Map.get(updated_object, field) != Map.get(orig_object_data, field)
data =
if Map.has_key?(updated_object, field) do
Map.put(data, field, updated_object[field])
else
Map.drop(data, [field])
end
%{data: data, updated: updated}
end
)
end
defp maybe_update_poll(to_be_updated, updated_object) do
choice_key = fn data ->
if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf"
end
with true <- to_be_updated["type"] == "Question",
key <- choice_key.(updated_object),
true <- key == choice_key.(to_be_updated),
orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])),
new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])),
true <- orig_choices == new_choices do
# Choices are the same, but counts are different
to_be_updated
|> Map.put(key, updated_object[key])
else
# Choices (or vote type) have changed, do not allow this
_ -> to_be_updated
end
end
defp handle_update_object( defp handle_update_object(
%{data: %{"type" => "Update", "object" => updated_object}} = object, %{data: %{"type" => "Update", "object" => updated_object}} = object,
meta meta
@ -462,14 +423,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
updated_object = meta[:object_data] updated_object = meta[:object_data]
if orig_object_data["type"] in @updatable_object_types do if orig_object_data["type"] in @updatable_object_types do
%{data: updated_object_data, updated: updated} = %{
orig_object_data updated_data: updated_object_data,
|> update_content_fields(updated_object) updated: updated,
used_history_in_new_object?: used_history_in_new_object?
updated_object_data = } = Object.Updater.make_new_object_data_from_update_object(orig_object_data, updated_object)
updated_object_data
|> Object.maybe_update_history(orig_object_data, updated)
|> maybe_update_poll(updated_object)
changeset = changeset =
orig_object orig_object
@ -481,6 +439,16 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, _} <- Object.set_cache(new_object), {:ok, _} <- Object.set_cache(new_object),
# The metadata/utils.ex uses the object id for the cache. # The metadata/utils.ex uses the object id for the cache.
{:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do
if used_history_in_new_object? do
with create_activity when not is_nil(create_activity) <-
Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id),
{:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do
nil
else
_ -> nil
end
end
if updated do if updated do
object object
|> Activity.normalize() |> Activity.normalize()

View file

@ -422,15 +422,7 @@ defmodule Pleroma.Web.CommonAPI do
with {:ok, draft} <- ActivityDraft.create(user, params) do with {:ok, draft} <- ActivityDraft.create(user, params) do
change = change =
Pleroma.Constants.status_updatable_fields() Object.Updater.make_update_object_data(orig_object.data, draft.object, Utils.make_date())
|> Enum.reduce(orig_object.data, fn key, acc ->
if Map.has_key?(draft.object, key) do
acc |> Map.put(key, Map.get(draft.object, key))
else
acc |> Map.drop([key])
end
end)
|> Map.put("updated", Utils.make_date())
{:ok, change} {:ok, change}
else else

View file

@ -274,7 +274,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
history_len = history_len =
1 + 1 +
(Object.history_for(object.data) (Object.Updater.history_for(object.data)
|> Map.get("orderedItems") |> Map.get("orderedItems")
|> length()) |> length())
@ -413,7 +413,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
user = CommonAPI.get_user(activity.data["actor"]) user = CommonAPI.get_user(activity.data["actor"])
past_history = past_history =
Object.history_for(object.data) Object.Updater.history_for(object.data)
|> Map.get("orderedItems") |> Map.get("orderedItems")
|> Enum.map(&Map.put(&1, "id", object.data["id"])) |> Enum.map(&Map.put(&1, "id", object.data["id"]))
|> Enum.map(&%Object{data: &1, id: object.id}) |> Enum.map(&%Object{data: &1, id: object.id})