From 4f530cabcf1bcadc09399a728975d329f3c9fdbf Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 31 Aug 2023 18:29:28 +0200 Subject: [PATCH] fix(reports): remove on delete cascade for reports Deleting an actor should not remove the reports Signed-off-by: Thomas Citharel --- lib/mobilizon/storage/views/instances.ex | 70 +++++++++++++++++++ ...3141104_add_instance_materialized_view.exs | 54 ++------------ ...8_remove_on_delete_cascade_for_reports.exs | 48 +++++++++++++ 3 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 lib/mobilizon/storage/views/instances.ex create mode 100644 priv/repo/migrations/20230831153358_remove_on_delete_cascade_for_reports.exs diff --git a/lib/mobilizon/storage/views/instances.ex b/lib/mobilizon/storage/views/instances.ex new file mode 100644 index 00000000..812d4348 --- /dev/null +++ b/lib/mobilizon/storage/views/instances.ex @@ -0,0 +1,70 @@ +defmodule Mobilizon.Storage.Views.Instances do + @moduledoc """ + SQL code for PostgreSQL instances materialized view + """ + + def create_materialized_view do + """ + CREATE MATERIALIZED VIEW instances AS + SELECT + a.domain, + COUNT(DISTINCT(p.id)) AS person_count, + COUNT(DISTINCT(g.id)) AS group_count, + COUNT(DISTINCT(e.id)) AS event_count, + COUNT(f1.id) AS followers_count, + COUNT(f2.id) AS followings_count, + COUNT(r.id) AS reports_count, + SUM(COALESCE((m.file->>'size')::int, 0)) AS media_size + FROM actors a + LEFT JOIN actors p ON a.id = p.id AND p.type = 'Person' + LEFT JOIN actors g ON a.id = g.id AND g.type = 'Group' + LEFT JOIN events e ON a.id = e.organizer_actor_id + LEFT JOIN followers f1 ON a.id = f1.actor_id + LEFT JOIN followers f2 ON a.id = f2.target_actor_id + LEFT JOIN reports r ON r.reported_id = a.id + LEFT JOIN medias m ON m.actor_id = a.id + WHERE a.domain IS NOT NULL + GROUP BY a.domain; + """ + end + + def refresh_instances do + """ + CREATE OR REPLACE FUNCTION refresh_instances() + RETURNS trigger AS $$ + BEGIN + REFRESH MATERIALIZED VIEW instances; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + """ + end + + def drop_trigger do + """ + DROP TRIGGER IF EXISTS refresh_instances_trigger ON actors; + """ + end + + def create_trigger do + """ + CREATE TRIGGER refresh_instances_trigger + AFTER INSERT OR UPDATE OR DELETE + ON actors + FOR EACH STATEMENT + EXECUTE PROCEDURE refresh_instances(); + """ + end + + def drop_refresh_instances do + """ + DROP FUNCTION IF EXISTS refresh_instances() CASCADE; + """ + end + + def drop_view do + """ + DROP MATERIALIZED VIEW IF EXISTS instances; + """ + end +end diff --git a/priv/repo/migrations/20211223141104_add_instance_materialized_view.exs b/priv/repo/migrations/20211223141104_add_instance_materialized_view.exs index a997321c..020f9206 100644 --- a/priv/repo/migrations/20211223141104_add_instance_materialized_view.exs +++ b/priv/repo/migrations/20211223141104_add_instance_materialized_view.exs @@ -1,51 +1,15 @@ defmodule Mobilizon.Storage.Repo.Migrations.AddInstanceMaterializedView do use Ecto.Migration + alias Mobilizon.Storage.Views.Instances def up do - execute(""" - CREATE MATERIALIZED VIEW instances AS - SELECT - a.domain, - COUNT(DISTINCT(p.id)) AS person_count, - COUNT(DISTINCT(g.id)) AS group_count, - COUNT(DISTINCT(e.id)) AS event_count, - COUNT(f1.id) AS followers_count, - COUNT(f2.id) AS followings_count, - COUNT(r.id) AS reports_count, - SUM(COALESCE((m.file->>'size')::int, 0)) AS media_size - FROM actors a - LEFT JOIN actors p ON a.id = p.id AND p.type = 'Person' - LEFT JOIN actors g ON a.id = g.id AND g.type = 'Group' - LEFT JOIN events e ON a.id = e.organizer_actor_id - LEFT JOIN followers f1 ON a.id = f1.actor_id - LEFT JOIN followers f2 ON a.id = f2.target_actor_id - LEFT JOIN reports r ON r.reported_id = a.id - LEFT JOIN medias m ON m.actor_id = a.id - WHERE a.domain IS NOT NULL - GROUP BY a.domain; - """) + execute(Instances.create_materialized_view()) - execute(""" - CREATE OR REPLACE FUNCTION refresh_instances() - RETURNS trigger AS $$ - BEGIN - REFRESH MATERIALIZED VIEW instances; - RETURN NULL; - END; - $$ LANGUAGE plpgsql; - """) + execute(Instances.refresh_instances()) - execute(""" - DROP TRIGGER IF EXISTS refresh_instances_trigger ON actors; - """) + execute(Instances.drop_trigger()) - execute(""" - CREATE TRIGGER refresh_instances_trigger - AFTER INSERT OR UPDATE OR DELETE - ON actors - FOR EACH STATEMENT - EXECUTE PROCEDURE refresh_instances(); - """) + execute(Instances.create_trigger()) create_if_not_exists(unique_index("instances", [:domain])) end @@ -53,12 +17,8 @@ defmodule Mobilizon.Storage.Repo.Migrations.AddInstanceMaterializedView do def down do drop_if_exists(unique_index("instances", [:domain])) - execute(""" - DROP FUNCTION IF EXISTS refresh_instances() CASCADE; - """) + execute(Instances.drop_refresh_instances()) - execute(""" - DROP MATERIALIZED VIEW IF EXISTS instances; - """) + execute(Instances.drop_view()) end end diff --git a/priv/repo/migrations/20230831153358_remove_on_delete_cascade_for_reports.exs b/priv/repo/migrations/20230831153358_remove_on_delete_cascade_for_reports.exs new file mode 100644 index 00000000..8b51bcf0 --- /dev/null +++ b/priv/repo/migrations/20230831153358_remove_on_delete_cascade_for_reports.exs @@ -0,0 +1,48 @@ +defmodule Mobilizon.Storage.Repo.Migrations.RemoveOnDeleteCascadeForReports do + use Ecto.Migration + alias Mobilizon.Storage.Views.Instances + + def up do + execute(Instances.drop_view()) + + drop(constraint(:reports, "reports_reported_id_fkey")) + drop(constraint(:reports, "reports_reporter_id_fkey")) + drop(constraint(:reports, "reports_manager_id_fkey")) + + alter table(:reports) do + modify(:reported_id, references(:actors, on_delete: :nilify_all), null: true) + modify(:reporter_id, references(:actors, on_delete: :nilify_all), null: true) + modify(:manager_id, references(:actors, on_delete: :nilify_all), null: true) + end + + drop(constraint(:report_notes, "report_notes_moderator_id_fkey")) + + alter table(:report_notes) do + modify(:moderator_id, references(:actors, on_delete: :nilify_all), null: true) + end + + execute(Instances.create_materialized_view()) + end + + def down do + execute(Instances.drop_view()) + + drop(constraint(:reports, "reports_reported_id_fkey")) + drop(constraint(:reports, "reports_reporter_id_fkey")) + drop(constraint(:reports, "reports_manager_id_fkey")) + + alter table(:reports) do + modify(:reported_id, references(:actors, on_delete: :delete_all), null: false) + modify(:reporter_id, references(:actors, on_delete: :delete_all), null: false) + modify(:manager_id, references(:actors, on_delete: :delete_all), null: true) + end + + drop(constraint(:report_notes, "report_notes_moderator_id_fkey")) + + alter table(:report_notes) do + modify(:moderator_id, references(:actors, on_delete: :delete_all), null: false) + end + + execute(Instances.create_materialized_view()) + end +end