Object: remove context_id field
30 to 70% of the objects in the object table are simple JSON objects containing a single field, 'id', being the context's ID. The reason for the creation of an object per context seems to be an old relic from the StatusNet era, and has only been used nowadays as an helper for threads in Pleroma-FE via the `pleroma.conversation_id` field in status views. An object per context was created, and its numerical ID (table column) was used and stored as 'context_id' in the object and activity along with the full 'context' URI/string. This commit removes this field and stops creation of objects for each context, which will also allow incoming activities to use activity IDs as contexts, something which was not possible before, or would have been very broken under most circumstances. The `pleroma.conversation_id` field has been reimplemented in a way to maintain backwards-compatibility by calculating a CRC32 of the full context URI/string in the object, instead of relying on the row ID for the created context object.
This commit is contained in:
parent
21ec1edbb6
commit
662a9e7518
14 changed files with 9 additions and 102 deletions
|
@ -208,10 +208,6 @@ def get_cached_by_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def context_mapping(context) do
|
|
||||||
Object.change(%Object{}, %{data: %{"id" => context}})
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
|
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
|
||||||
%ObjectTombstone{
|
%ObjectTombstone{
|
||||||
id: id,
|
id: id,
|
||||||
|
|
|
@ -174,7 +174,7 @@ def changeset(struct, data) do
|
||||||
defp validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Article", "Note", "Page"])
|
|> validate_inclusion(:type, ["Article", "Note", "Page"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context])
|
||||||
|> CommonValidations.validate_any_presence([:cc, :to])
|
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||||
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
||||||
|> CommonValidations.validate_actor_presence()
|
|> CommonValidations.validate_actor_presence()
|
||||||
|
|
|
@ -22,14 +22,12 @@ def cast_and_filter_recipients(message, field, follower_collection, field_fallba
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_object_defaults(data) do
|
def fix_object_defaults(data) do
|
||||||
%{data: %{"id" => context}, id: context_id} =
|
context = Utils.maybe_create_context(data["context"] || data["conversation"])
|
||||||
Utils.create_context(data["context"] || data["conversation"])
|
|
||||||
|
|
||||||
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
||||||
|
|
||||||
data
|
data
|
||||||
|> Map.put("context", context)
|
|> Map.put("context", context)
|
||||||
|> Map.put("context_id", context_id)
|
|
||||||
|> cast_and_filter_recipients("to", follower_collection)
|
|> cast_and_filter_recipients("to", follower_collection)
|
||||||
|> cast_and_filter_recipients("cc", follower_collection)
|
|> cast_and_filter_recipients("cc", follower_collection)
|
||||||
|> cast_and_filter_recipients("bto", follower_collection)
|
|> cast_and_filter_recipients("bto", follower_collection)
|
||||||
|
|
|
@ -62,7 +62,7 @@ def changeset(struct, data) do
|
||||||
defp validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Event"])
|
|> validate_inclusion(:type, ["Event"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context])
|
||||||
|> CommonValidations.validate_any_presence([:cc, :to])
|
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||||
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
||||||
|> CommonValidations.validate_actor_presence()
|
|> CommonValidations.validate_actor_presence()
|
||||||
|
|
|
@ -80,7 +80,7 @@ def changeset(struct, data) do
|
||||||
defp validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Question"])
|
|> validate_inclusion(:type, ["Question"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context])
|
||||||
|> CommonValidations.validate_any_presence([:cc, :to])
|
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||||
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
||||||
|> CommonValidations.validate_actor_presence()
|
|> CommonValidations.validate_actor_presence()
|
||||||
|
|
|
@ -154,22 +154,7 @@ def get_notified_from_object(object) do
|
||||||
Notification.get_notified_from_activity(%Activity{data: object}, false)
|
Notification.get_notified_from_activity(%Activity{data: object}, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_context(context) do
|
def maybe_create_context(context), do: context || generate_id("contexts")
|
||||||
context = context || generate_id("contexts")
|
|
||||||
|
|
||||||
# Ecto has problems accessing the constraint inside the jsonb,
|
|
||||||
# so we explicitly check for the existed object before insert
|
|
||||||
object = Object.get_cached_by_ap_id(context)
|
|
||||||
|
|
||||||
with true <- is_nil(object),
|
|
||||||
changeset <- Object.context_mapping(context),
|
|
||||||
{:ok, inserted_object} <- Repo.insert(changeset) do
|
|
||||||
inserted_object
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Enqueues an activity for federation if it's local
|
Enqueues an activity for federation if it's local
|
||||||
|
@ -201,18 +186,16 @@ def lazy_put_activity_defaults(map, true) do
|
||||||
|> Map.put_new("id", "pleroma:fakeid")
|
|> Map.put_new("id", "pleroma:fakeid")
|
||||||
|> Map.put_new_lazy("published", &make_date/0)
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|> Map.put_new("context", "pleroma:fakecontext")
|
|> Map.put_new("context", "pleroma:fakecontext")
|
||||||
|> Map.put_new("context_id", -1)
|
|
||||||
|> lazy_put_object_defaults(true)
|
|> lazy_put_object_defaults(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def lazy_put_activity_defaults(map, _fake?) do
|
def lazy_put_activity_defaults(map, _fake?) do
|
||||||
%{data: %{"id" => context}, id: context_id} = create_context(map["context"])
|
context = maybe_create_context(map["context"])
|
||||||
|
|
||||||
map
|
map
|
||||||
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
||||||
|> Map.put_new_lazy("published", &make_date/0)
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|> Map.put_new("context", context)
|
|> Map.put_new("context", context)
|
||||||
|> Map.put_new("context_id", context_id)
|
|
||||||
|> lazy_put_object_defaults(false)
|
|> lazy_put_object_defaults(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -226,7 +209,6 @@ defp lazy_put_object_defaults(%{"object" => map} = activity, true)
|
||||||
|> Map.put_new("id", "pleroma:fake_object_id")
|
|> Map.put_new("id", "pleroma:fake_object_id")
|
||||||
|> Map.put_new_lazy("published", &make_date/0)
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|> Map.put_new("context", activity["context"])
|
|> Map.put_new("context", activity["context"])
|
||||||
|> Map.put_new("context_id", activity["context_id"])
|
|
||||||
|> Map.put_new("fake", true)
|
|> Map.put_new("fake", true)
|
||||||
|
|
||||||
%{activity | "object" => object}
|
%{activity | "object" => object}
|
||||||
|
@ -239,7 +221,6 @@ defp lazy_put_object_defaults(%{"object" => map} = activity, _)
|
||||||
|> Map.put_new_lazy("id", &generate_object_id/0)
|
|> Map.put_new_lazy("id", &generate_object_id/0)
|
||||||
|> Map.put_new_lazy("published", &make_date/0)
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|> Map.put_new("context", activity["context"])
|
|> Map.put_new("context", activity["context"])
|
||||||
|> Map.put_new("context_id", activity["context_id"])
|
|
||||||
|
|
||||||
%{activity | "object" => object}
|
%{activity | "object" => object}
|
||||||
end
|
end
|
||||||
|
|
|
@ -458,35 +458,6 @@ def get_report_statuses(%User{ap_id: actor}, %{status_ids: status_ids})
|
||||||
|
|
||||||
def get_report_statuses(_, _), do: {:ok, nil}
|
def get_report_statuses(_, _), do: {:ok, nil}
|
||||||
|
|
||||||
# DEPRECATED mostly, context objects are now created at insertion time.
|
|
||||||
def context_to_conversation_id(context) do
|
|
||||||
with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
|
|
||||||
id
|
|
||||||
else
|
|
||||||
_e ->
|
|
||||||
changeset = Object.context_mapping(context)
|
|
||||||
|
|
||||||
case Repo.insert(changeset) do
|
|
||||||
{:ok, %{id: id}} ->
|
|
||||||
id
|
|
||||||
|
|
||||||
# This should be solved by an upsert, but it seems ecto
|
|
||||||
# has problems accessing the constraint inside the jsonb.
|
|
||||||
{:error, _} ->
|
|
||||||
Object.get_cached_by_ap_id(context).id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def conversation_id_to_context(id) do
|
|
||||||
with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
|
|
||||||
context
|
|
||||||
else
|
|
||||||
_e ->
|
|
||||||
{:error, dgettext("errors", "No such conversation")}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_character_limit("" = _full_payload, [] = _attachments) do
|
def validate_character_limit("" = _full_payload, [] = _attachments) do
|
||||||
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
|
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,7 +61,7 @@ defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(cont
|
||||||
do: context_id
|
do: context_id
|
||||||
|
|
||||||
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
|
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
|
||||||
do: Utils.context_to_conversation_id(context)
|
do: :erlang.crc32(context)
|
||||||
|
|
||||||
defp get_context_id(_), do: nil
|
defp get_context_id(_), do: nil
|
||||||
|
|
||||||
|
|
|
@ -523,7 +523,6 @@ test "inserts a given map into the activity database, giving it an id if it has
|
||||||
assert activity.data["ok"] == data["ok"]
|
assert activity.data["ok"] == data["ok"]
|
||||||
assert activity.data["id"] == given_id
|
assert activity.data["id"] == given_id
|
||||||
assert activity.data["context"] == "blabla"
|
assert activity.data["context"] == "blabla"
|
||||||
assert activity.data["context_id"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "adds a context when none is there" do
|
test "adds a context when none is there" do
|
||||||
|
@ -545,8 +544,6 @@ test "adds a context when none is there" do
|
||||||
|
|
||||||
assert is_binary(activity.data["context"])
|
assert is_binary(activity.data["context"])
|
||||||
assert is_binary(object.data["context"])
|
assert is_binary(object.data["context"])
|
||||||
assert activity.data["context_id"]
|
|
||||||
assert object.data["context_id"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
|
test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
|
||||||
|
@ -1560,7 +1557,7 @@ test "it can create a Flag activity",
|
||||||
})
|
})
|
||||||
|
|
||||||
assert Repo.aggregate(Activity, :count, :id) == 1
|
assert Repo.aggregate(Activity, :count, :id) == 1
|
||||||
assert Repo.aggregate(Object, :count, :id) == 2
|
assert Repo.aggregate(Object, :count, :id) == 1
|
||||||
assert Repo.aggregate(Notification, :count, :id) == 0
|
assert Repo.aggregate(Notification, :count, :id) == 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,8 +33,6 @@ test "Mastodon Question activity" do
|
||||||
assert object.data["context"] ==
|
assert object.data["context"] ==
|
||||||
"tag:mastodon.sdf.org,2019-05-10:objectId=15095122:objectType=Conversation"
|
"tag:mastodon.sdf.org,2019-05-10:objectId=15095122:objectType=Conversation"
|
||||||
|
|
||||||
assert object.data["context_id"]
|
|
||||||
|
|
||||||
assert object.data["anyOf"] == []
|
assert object.data["anyOf"] == []
|
||||||
|
|
||||||
assert Enum.sort(object.data["oneOf"]) ==
|
assert Enum.sort(object.data["oneOf"]) ==
|
||||||
|
@ -68,7 +66,6 @@ test "Mastodon Question activity" do
|
||||||
reply_object = Object.normalize(reply_activity, fetch: false)
|
reply_object = Object.normalize(reply_activity, fetch: false)
|
||||||
|
|
||||||
assert reply_object.data["context"] == object.data["context"]
|
assert reply_object.data["context"] == object.data["context"]
|
||||||
assert reply_object.data["context_id"] == object.data["context_id"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Mastodon Question activity with HTML tags in plaintext" do
|
test "Mastodon Question activity with HTML tags in plaintext" do
|
||||||
|
|
|
@ -232,7 +232,6 @@ test "it strips internal fields" do
|
||||||
assert is_nil(modified["object"]["like_count"])
|
assert is_nil(modified["object"]["like_count"])
|
||||||
assert is_nil(modified["object"]["announcements"])
|
assert is_nil(modified["object"]["announcements"])
|
||||||
assert is_nil(modified["object"]["announcement_count"])
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
assert is_nil(modified["object"]["context_id"])
|
|
||||||
assert is_nil(modified["object"]["generator"])
|
assert is_nil(modified["object"]["generator"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -247,7 +246,6 @@ test "it strips internal fields of article" do
|
||||||
assert is_nil(modified["object"]["like_count"])
|
assert is_nil(modified["object"]["like_count"])
|
||||||
assert is_nil(modified["object"]["announcements"])
|
assert is_nil(modified["object"]["announcements"])
|
||||||
assert is_nil(modified["object"]["announcement_count"])
|
assert is_nil(modified["object"]["announcement_count"])
|
||||||
assert is_nil(modified["object"]["context_id"])
|
|
||||||
assert is_nil(modified["object"]["likes"])
|
assert is_nil(modified["object"]["likes"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -429,7 +429,6 @@ test "returns map with id and published data" do
|
||||||
object = Object.normalize(note_activity, fetch: false)
|
object = Object.normalize(note_activity, fetch: false)
|
||||||
res = Utils.lazy_put_activity_defaults(%{"context" => object.data["id"]})
|
res = Utils.lazy_put_activity_defaults(%{"context" => object.data["id"]})
|
||||||
assert res["context"] == object.data["id"]
|
assert res["context"] == object.data["id"]
|
||||||
assert res["context_id"] == object.id
|
|
||||||
assert res["id"]
|
assert res["id"]
|
||||||
assert res["published"]
|
assert res["published"]
|
||||||
end
|
end
|
||||||
|
@ -437,7 +436,6 @@ test "returns map with id and published data" do
|
||||||
test "returns map with fake id and published data" do
|
test "returns map with fake id and published data" do
|
||||||
assert %{
|
assert %{
|
||||||
"context" => "pleroma:fakecontext",
|
"context" => "pleroma:fakecontext",
|
||||||
"context_id" => -1,
|
|
||||||
"id" => "pleroma:fakeid",
|
"id" => "pleroma:fakeid",
|
||||||
"published" => _
|
"published" => _
|
||||||
} = Utils.lazy_put_activity_defaults(%{}, true)
|
} = Utils.lazy_put_activity_defaults(%{}, true)
|
||||||
|
@ -454,13 +452,11 @@ test "returns activity data with object" do
|
||||||
})
|
})
|
||||||
|
|
||||||
assert res["context"] == object.data["id"]
|
assert res["context"] == object.data["id"]
|
||||||
assert res["context_id"] == object.id
|
|
||||||
assert res["id"]
|
assert res["id"]
|
||||||
assert res["published"]
|
assert res["published"]
|
||||||
assert res["object"]["id"]
|
assert res["object"]["id"]
|
||||||
assert res["object"]["published"]
|
assert res["object"]["published"]
|
||||||
assert res["object"]["context"] == object.data["id"]
|
assert res["object"]["context"] == object.data["id"]
|
||||||
assert res["object"]["context_id"] == object.id
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -273,22 +273,6 @@ test "delegated renderers" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "context_to_conversation_id" do
|
|
||||||
test "creates a mapping object" do
|
|
||||||
conversation_id = Utils.context_to_conversation_id("random context")
|
|
||||||
object = Object.get_by_ap_id("random context")
|
|
||||||
|
|
||||||
assert conversation_id == object.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns an existing mapping for an existing object" do
|
|
||||||
{:ok, object} = Object.context_mapping("random context") |> Repo.insert()
|
|
||||||
conversation_id = Utils.context_to_conversation_id("random context")
|
|
||||||
|
|
||||||
assert conversation_id == object.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "formats date to asctime" do
|
describe "formats date to asctime" do
|
||||||
test "when date is in ISO 8601 format" do
|
test "when date is in ISO 8601 format" do
|
||||||
date = DateTime.utc_now() |> DateTime.to_iso8601()
|
date = DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
|
@ -517,17 +501,6 @@ test "returns empty string when date invalid" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "conversation_id_to_context/1" do
|
|
||||||
test "returns id" do
|
|
||||||
object = insert(:note)
|
|
||||||
assert Utils.conversation_id_to_context(object.id) == object.data["id"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns error if object not found" do
|
|
||||||
assert Utils.conversation_id_to_context("123") == {:error, "No such conversation"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "maybe_notify_mentioned_recipients/2" do
|
describe "maybe_notify_mentioned_recipients/2" do
|
||||||
test "returns recipients when activity is not `Create`" do
|
test "returns recipients when activity is not `Create`" do
|
||||||
activity = insert(:like_activity)
|
activity = insert(:like_activity)
|
||||||
|
|
|
@ -240,7 +240,7 @@ test "a note activity" do
|
||||||
object_data = Object.normalize(note, fetch: false).data
|
object_data = Object.normalize(note, fetch: false).data
|
||||||
user = User.get_cached_by_ap_id(note.data["actor"])
|
user = User.get_cached_by_ap_id(note.data["actor"])
|
||||||
|
|
||||||
convo_id = Utils.context_to_conversation_id(object_data["context"])
|
convo_id = :erlang.crc32(object_data["context"])
|
||||||
|
|
||||||
status = StatusView.render("show.json", %{activity: note})
|
status = StatusView.render("show.json", %{activity: note})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue