diff options
12 files changed, 195 insertions, 38 deletions
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index a973beaa9..41a36851e 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -33,7 +33,7 @@ defmodule Mix.Tasks.Pleroma.Database do Logger.info("Removing embedded objects") Repo.query!( - "update activities set data = safe_jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;", + "update objects set data = safe_jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;", [], timeout: :infinity ) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 4106feef6..30d8f7092 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Activity do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - schema "activities" do + schema "objects" do field(:data, :map) field(:local, :boolean, default: true) field(:actor, :string) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 9f51a3062..3b7ff0554 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1161,7 +1161,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_instance(query, %{instance: instance}) when is_binary(instance) do from( activity in query, - where: fragment("split_part(actor::text, '/'::text, 3) = ?", ^instance) + where: fragment("split_part(?::text, '/'::text, 3) = ?", activity.actor, ^instance) ) end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index c1f6b2b49..d5f0a3245 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -712,9 +712,10 @@ defmodule Pleroma.Web.ActivityPub.Utils do defp build_flag_object(%{statuses: statuses}) do Enum.map(statuses || [], &build_flag_object/1) + |> Enum.reject(&is_nil/1) end - defp build_flag_object(%Activity{data: %{"id" => id}, object: %{data: data}}) do + defp build_flag_object(%Activity{data: %{"id" => id, "type" => "Create"}, object: %{data: data}}) do activity_actor = User.get_by_ap_id(data["actor"]) %{ @@ -730,28 +731,26 @@ defmodule Pleroma.Web.ActivityPub.Utils do } end - defp build_flag_object(act) when is_map(act) or is_binary(act) do - id = - case act do - %Activity{} = act -> act.data["id"] - act when is_map(act) -> act["id"] - act when is_binary(act) -> act - end + defp build_flag_object(%{data: %{"id" => id}}), do: build_flag_object(id) + defp build_flag_object(%{"id" => id}), do: build_flag_object(id) - case Activity.get_by_ap_id_with_object(id) do - %Activity{} = activity -> + defp build_flag_object(ap_id) when is_binary(ap_id) do + case Activity.get_by_ap_id_with_object(ap_id) do + %Activity{data: %{"type" => "Create"}} = activity -> build_flag_object(activity) - nil -> - if activity = Activity.get_by_object_ap_id_with_object(id) do - build_flag_object(activity) - else - %{"id" => id, "deleted" => true} + _ -> + case Activity.get_by_object_ap_id_with_object(ap_id) do + %Activity{data: %{"type" => "Create"}} = activity -> + build_flag_object(activity) + + _ -> + %{"id" => ap_id, "deleted" => true} end end end - defp build_flag_object(_), do: [] + defp build_flag_object(_), do: nil #### Report-related helpers def get_reports(params, page, page_size) do diff --git a/priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs b/priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs index 9e95a8111..a2010e188 100644 --- a/priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs +++ b/priv/repo/migrations/20200527163635_delete_notifications_from_invisible_users.exs @@ -6,7 +6,7 @@ defmodule Pleroma.Repo.Migrations.DeleteNotificationsFromInvisibleUsers do def up do Pleroma.Notification - |> join(:inner, [n], activity in assoc(n, :activity)) + |> join(:inner, [n], activity in "activities") |> where( [n, a], fragment("? in (SELECT ap_id FROM users WHERE invisible = true)", a.actor) diff --git a/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs b/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs index 9333fc5a1..252eb0716 100644 --- a/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs +++ b/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs @@ -7,7 +7,7 @@ defmodule Pleroma.Repo.Migrations.DeleteNotificationWithoutActivity do def up do from( q in Pleroma.Notification, - left_join: c in assoc(q, :activity), + left_join: c in "activities", select: %{id: type(q.id, :integer)}, where: is_nil(c.id) ) diff --git a/priv/repo/migrations/20211218181647_add_activity_fields_to_objects.exs b/priv/repo/migrations/20211218181647_add_activity_fields_to_objects.exs new file mode 100644 index 000000000..d53e514f7 --- /dev/null +++ b/priv/repo/migrations/20211218181647_add_activity_fields_to_objects.exs @@ -0,0 +1,138 @@ +defmodule Pleroma.Repo.Migrations.AddActivityFieldsToObjects do + use Ecto.Migration + + @function_name "update_status_visibility_counter_cache" + @trigger_name "status_visibility_counter_cache_trigger" + + def up do + alter table(:objects) do + add(:local, :boolean) + add(:actor, :string) + add(:recipients, {:array, :string}) + end + + create_if_not_exists(index(:objects, [:local])) + create_if_not_exists(index(:objects, [:actor, "id DESC NULLS LAST"])) + create_if_not_exists(index(:objects, [:recipients], using: :gin)) + + create_if_not_exists( + index(:objects, ["(data->'to')"], name: :activities_to_index, using: :gin) + ) + + create_if_not_exists( + index(:objects, ["(data->'cc')"], name: :activities_cc_index, using: :gin) + ) + + # Copy all activities into the newly formatted objects table + execute("INSERT INTO objects (SELECT * FROM activities)") + + # Update notifications foreign key + execute("alter table notifications drop constraint notifications_activity_id_fkey") + + execute( + "alter table notifications add constraint notifications_object_id_fkey foreign key (activity_id) references objects(id) on delete cascade" + ) + + # Update bookmarks foreign key + execute("alter table bookmarks drop constraint bookmarks_activity_id_fkey") + + execute( + "alter table bookmarks add constraint bookmarks_object_id_fkey foreign key (activity_id) references objects(id) on delete cascade" + ) + + # Update report notes foreign key + execute("alter table report_notes drop constraint report_notes_activity_id_fkey") + + execute( + "alter table report_notes add constraint report_notes_object_id_fkey foreign key (activity_id) references objects(id)" + ) + + # Nuke the old activities table + execute("drop table activities") + + # Update triggers + """ + CREATE TRIGGER #{@trigger_name} + BEFORE + INSERT + OR UPDATE of recipients, data + OR DELETE + ON objects + FOR EACH ROW + EXECUTE PROCEDURE #{@function_name}(); + """ + |> execute() + + execute("drop function if exists thread_visibility(actor varchar, activity_id varchar)") + execute(update_thread_visibility()) + end + + def down do + raise "Lol, there's no going back from this." + end + + # It acts upon objects instead of activities now + def update_thread_visibility do + """ + CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, object_id varchar) RETURNS boolean AS $$ + DECLARE + public varchar := 'https://www.w3.org/ns/activitystreams#Public'; + child objects%ROWTYPE; + object objects%ROWTYPE; + author_fa varchar; + valid_recipients varchar[]; + actor_user_following varchar[]; + BEGIN + --- Fetch actor following + SELECT array_agg(following.follower_address) INTO actor_user_following FROM following_relationships + JOIN users ON users.id = following_relationships.follower_id + JOIN users AS following ON following.id = following_relationships.following_id + WHERE users.ap_id = actor; + + --- Fetch our initial object. + SELECT * INTO object FROM objects WHERE objects.data->>'id' = object_id; + + LOOP + --- Ensure that we have an object before continuing. + --- If we don't, the thread is not satisfiable. + IF object IS NULL THEN + RETURN false; + END IF; + + --- We only care about Create objects. + IF object.data->>'type' != 'Create' THEN + RETURN true; + END IF; + + --- Normalize the child object into child. + SELECT * INTO child FROM objects + WHERE COALESCE(object.data->'object'->>'id', object.data->>'object') = objects.data->>'id'; + + --- Fetch the author's AS2 following collection. + SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = object.actor; + + --- Prepare valid recipients array. + valid_recipients := ARRAY[actor, public]; + IF ARRAY[author_fa] && actor_user_following THEN + valid_recipients := valid_recipients || author_fa; + END IF; + + --- Check visibility. + IF NOT valid_recipients && object.recipients THEN + --- object not visible, break out of the loop + RETURN false; + END IF; + + --- If there's a parent, load it and do this all over again. + IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN + SELECT * INTO object FROM objects + WHERE child.data->>'inReplyTo' = objects.data->>'id'; + ELSE + RETURN true; + END IF; + END LOOP; + END; + $$ LANGUAGE plpgsql IMMUTABLE; + """ + end +end diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs index 4f9144f91..7d0e427fa 100644 --- a/test/pleroma/activity_test.exs +++ b/test/pleroma/activity_test.exs @@ -51,7 +51,8 @@ defmodule Pleroma.ActivityTest do {:ok, bookmark3} = Bookmark.create(user3.id, activity.id) queried_activity = - Ecto.Query.from(Pleroma.Activity) + Activity + |> Ecto.Query.where(id: ^activity.id) |> Activity.with_preloaded_bookmark(user3) |> Repo.one() @@ -64,17 +65,19 @@ defmodule Pleroma.ActivityTest do annoyed_user = insert(:user) {:ok, _} = ThreadMute.add_mute(annoyed_user.id, activity.data["context"]) + query = Ecto.Query.where(Activity, id: ^activity.id) + activity_with_unset_thread_muted_field = - Ecto.Query.from(Activity) + query |> Repo.one() activity_for_user = - Ecto.Query.from(Activity) + query |> Activity.with_set_thread_muted_field(user) |> Repo.one() activity_for_annoyed_user = - Ecto.Query.from(Activity) + query |> Activity.with_set_thread_muted_field(annoyed_user) |> Repo.one() @@ -90,7 +93,7 @@ defmodule Pleroma.ActivityTest do {:ok, bookmark} = Bookmark.create(user.id, activity.id) queried_activity = - Ecto.Query.from(Pleroma.Activity) + Ecto.Query.where(Activity, id: ^activity.id) |> Activity.with_preloaded_bookmark(user) |> Repo.one() @@ -103,7 +106,7 @@ defmodule Pleroma.ActivityTest do {:ok, bookmark} = Bookmark.create(user.id, activity.id) queried_activity = - Ecto.Query.from(Pleroma.Activity) + Ecto.Query.where(Activity, id: ^activity.id) |> Repo.one() assert Activity.get_bookmark(queried_activity, user) == bookmark @@ -266,7 +269,11 @@ defmodule Pleroma.ActivityTest do insert(:add_activity, user: user, note: note) insert(:add_activity, user: user) - assert Repo.aggregate(Activity, :count, :id) == 4 + activities_query = + Activity + |> Ecto.Query.where(fragment("data->>'type' IN ('Create', 'Add')")) + + assert Repo.aggregate(activities_query, :count, :id) == 4 add_query = Activity.add_by_params_query(note.data["object"], user.ap_id, user.featured_address) @@ -276,6 +283,6 @@ defmodule Pleroma.ActivityTest do Repo.delete_all(add_query) assert Repo.aggregate(add_query, :count, :id) == 0 - assert Repo.aggregate(Activity, :count, :id) == 2 + assert Repo.aggregate(activities_query, :count, :id) == 2 end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 6cd93c34c..607d81470 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -19,6 +19,8 @@ defmodule Pleroma.UserTest do import ExUnit.CaptureLog import Swoosh.TestAssertions + require Ecto.Query + setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok @@ -436,7 +438,11 @@ defmodule Pleroma.UserTest do {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() - activity = Repo.one(Pleroma.Activity) + activity = + Activity + |> Ecto.Query.where(fragment("data->>'type' = 'Create'")) + |> Repo.one() + assert registered_user.ap_id in activity.recipients assert Object.normalize(activity, fetch: false).data["content"] =~ "direct message" assert activity.actor == welcome_user.ap_id @@ -452,7 +458,11 @@ defmodule Pleroma.UserTest do {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() - activity = Repo.one(Pleroma.Activity) + activity = + Activity + |> Ecto.Query.where(fragment("data->>'type' = 'Create'")) + |> Repo.one() + assert registered_user.ap_id in activity.recipients assert Object.normalize(activity, fetch: false).data["content"] =~ "chat message" assert activity.actor == welcome_user.ap_id @@ -491,7 +501,11 @@ defmodule Pleroma.UserTest do {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() - activity = Repo.one(Pleroma.Activity) + activity = + Activity + |> Ecto.Query.where(fragment("data->>'type' = 'Create'")) + |> Repo.one() + assert registered_user.ap_id in activity.recipients assert Object.normalize(activity, fetch: false).data["content"] =~ "chat message" assert activity.actor == welcome_user.ap_id diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 50315e21f..30ea6be50 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -1143,7 +1143,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) - assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2 + assert Pleroma.Repo.aggregate(Object, :count, :id) == 4 ObanHelpers.perform_all() diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 574ef0d71..7313ed42e 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1394,8 +1394,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1}) |> Enum.map(fn a -> a.id end) - assert [public_activity.id, private_activity_1.id] == activities - assert length(activities) == 2 + assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities + assert length(activities) == 3 end end @@ -1514,8 +1514,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do content: content }) - assert Repo.aggregate(Activity, :count, :id) == 1 - assert Repo.aggregate(Object, :count, :id) == 2 + assert Repo.aggregate(Object, :count, :id) == 3 assert Repo.aggregate(Notification, :count, :id) == 0 end end diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs index 99cc7bbd0..e480a0118 100644 --- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs @@ -363,7 +363,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do [note, _] = notes assert note["user"]["nickname"] == admin.nickname - assert note["content"] == "this is disgusting!" + assert note["content"] =~ "this is disgusting" assert note["created_at"] assert response["total"] == 1 end |