forked from AkkomaGang/akkoma
make conversation-id deterministic (#154)
Reviewed-on: AkkomaGang/akkoma#154
This commit is contained in:
parent
21ec1edbb6
commit
62e179f446
16 changed files with 23 additions and 109 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()
|
||||||
|
|
|
@ -51,8 +51,6 @@ defmacro status_object_fields do
|
||||||
field(:summary, :string)
|
field(:summary, :string)
|
||||||
|
|
||||||
field(:context, :string)
|
field(:context, :string)
|
||||||
# short identifier for PleromaFE to group statuses by context
|
|
||||||
field(:context_id, :integer)
|
|
||||||
|
|
||||||
field(:sensitive, :boolean, default: false)
|
field(:sensitive, :boolean, default: false)
|
||||||
field(:replies_count, :integer, default: 0)
|
field(:replies_count, :integer, default: 0)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -57,11 +57,8 @@ defp get_replied_to_activities(activities) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.RemoveNullObjects do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
statement = """
|
||||||
|
DELETE FROM objects
|
||||||
|
WHERE (data->>'type') is null;
|
||||||
|
"""
|
||||||
|
|
||||||
|
execute(statement)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down, do: :ok
|
||||||
|
end
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.CommonAPI.UtilsTest do
|
defmodule Pleroma.Web.CommonAPI.UtilsTest do
|
||||||
alias Pleroma.Builders.UserBuilder
|
alias Pleroma.Builders.UserBuilder
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.ActivityDraft
|
alias Pleroma.Web.CommonAPI.ActivityDraft
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
@ -273,22 +272,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 +500,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)
|
||||||
|
|
|
@ -14,7 +14,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserRelationship
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
@ -240,7 +239,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