diff options
author | Alex Gleason <alex@alexgleason.me> | 2021-12-18 18:26:40 -0600 |
---|---|---|
committer | Alex Gleason <alex@alexgleason.me> | 2021-12-18 18:26:40 -0600 |
commit | 0f81adf4486a76772dd2bed0a3671f3604c1e31b (patch) | |
tree | e8458d18b2cf0a1cb5c41960c2fc42fbe4b7a9a5 | |
parent | 0dc132a543f1d96ab9892e6638596e7120a5254f (diff) | |
download | pleroma-0f81adf4486a76772dd2bed0a3671f3604c1e31b.tar.gz |
Combine Activities and Objects into one table
5 files changed, 129 insertions, 4 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/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..b003fe814 --- /dev/null +++ b/priv/repo/migrations/20211218181647_add_activity_fields_to_objects.exs @@ -0,0 +1,125 @@ +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 + INNER JOIN objects ON COALESCE(objects.data->'object'->>'id', objects.data->>'object') = objects.data->>'id' + 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 + INNER JOIN objects ON COALESCE(objects.data->'object'->>'id', objects.data->>'object') = objects.data->>'id' + WHERE child.data->>'inReplyTo' = objects.data->>'id'; + ELSE + RETURN true; + END IF; + END LOOP; + END; + $$ LANGUAGE plpgsql IMMUTABLE; + """ + end +end |