Add a migration to fix blocked follows
authorEgor Kislitsyn <egor@kislitsyn.com>
Wed, 30 Oct 2019 08:52:37 +0000 (15:52 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 30 Oct 2019 08:52:37 +0000 (15:52 +0700)
priv/repo/migrations/20191029172832_fix_blocked_follows.exs [new file with mode: 0644]

diff --git a/priv/repo/migrations/20191029172832_fix_blocked_follows.exs b/priv/repo/migrations/20191029172832_fix_blocked_follows.exs
new file mode 100644 (file)
index 0000000..71f8f13
--- /dev/null
@@ -0,0 +1,112 @@
+defmodule Pleroma.Repo.Migrations.FixBlockedFollows do
+  use Ecto.Migration
+
+  import Ecto.Query
+  alias Pleroma.Config
+  alias Pleroma.Repo
+
+  def up do
+    unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
+
+    if unfollow_blocked do
+      "activities"
+      |> where([activity], fragment("? ->> 'type' = 'Block'", activity.data))
+      |> distinct([activity], [
+        activity.actor,
+        fragment(
+          "coalesce((?)->'object'->>'id', (?)->>'object')",
+          activity.data,
+          activity.data
+        )
+      ])
+      |> order_by([activity], [fragment("? desc nulls last", activity.id)])
+      |> select([activity], %{
+        blocker: activity.actor,
+        blocked:
+          fragment("coalesce((?)->'object'->>'id', (?)->>'object')", activity.data, activity.data),
+        created_at: activity.id
+      })
+      |> Repo.stream()
+      |> Enum.map(&unfollow_if_blocked/1)
+      |> Enum.uniq()
+      |> Enum.each(&update_follower_count/1)
+    end
+  end
+
+  def down do
+  end
+
+  def unfollow_if_blocked(%{blocker: blocker_id, blocked: blocked_id, created_at: blocked_at}) do
+    query =
+      from(
+        activity in "activities",
+        where: fragment("? ->> 'type' = 'Follow'", activity.data),
+        where: activity.actor == ^blocked_id,
+        # this is to use the index
+        where:
+          fragment(
+            "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
+            activity.data,
+            activity.data,
+            ^blocker_id
+          ),
+        where: activity.id > ^blocked_at,
+        where: fragment("(?)->>'state' = 'accept'", activity.data),
+        order_by: [fragment("? desc nulls last", activity.id)]
+      )
+
+    unless Repo.exists?(query) do
+      blocker = "users" |> select([:id, :local]) |> Repo.get_by(ap_id: blocker_id)
+      blocked = "users" |> select([:id]) |> Repo.get_by(ap_id: blocked_id)
+
+      if !is_nil(blocker) && !is_nil(blocked) do
+        unfollow(blocked, blocker)
+      end
+    end
+  end
+
+  def unfollow(%{id: follower_id}, %{id: followed_id} = followed) do
+    following_relationship =
+      "following_relationships"
+      |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
+      |> select([:id])
+      |> Repo.one()
+
+    case following_relationship do
+      nil ->
+        {:ok, nil}
+
+      %{id: following_relationship_id} ->
+        "following_relationships"
+        |> where(id: ^following_relationship_id)
+        |> Repo.delete_all()
+
+        followed
+    end
+  end
+
+  def update_follower_count(%{id: user_id} = user) do
+    if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
+      follower_count_query =
+        "users"
+        |> where([u], u.id != ^user_id)
+        |> where([u], u.deactivated != ^true)
+        |> join(:inner, [u], r in "following_relationships",
+          as: :relationships,
+          on: r.following_id == ^user_id and r.follower_id == u.id
+        )
+        |> where([relationships: r], r.state == "accept")
+        |> select([u], %{count: count(u.id)})
+
+      "users"
+      |> where(id: ^user_id)
+      |> join(:inner, [u], s in subquery(follower_count_query))
+      |> update([u, s],
+        set: [follower_count: s.count]
+      )
+      |> Repo.update_all([])
+    end
+  end
+
+  def update_follower_count(_), do: :noop
+end