diff --git a/README.md b/README.md
index 846442346..dd49822e9 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ While we don’t provide docker files, other people have written very good ones.
 
 ### Dependencies
 
-* Postgresql version 9.6 or newer
+* Postgresql version 9.6 or newer, including the contrib modules
 * Elixir version 1.7 or newer. If your distribution only has an old version available, check [Elixir’s install page](https://elixir-lang.org/install.html) or use a tool like [asdf](https://github.com/asdf-vm/asdf).
 * Build-essential tools
 
@@ -71,7 +71,7 @@ This is useful for running Pleroma inside Tor or I2P.
 
 ## Customization and contribution
 
-The [Pleroma Documentation](https://docs-develop.pleroma.social/readme.html) offers manuals and guides on how to further customize your instance to your liking and how you can contribute to the project.
+The [Pleroma Documentation](https://docs-develop.pleroma.social) offers manuals and guides on how to further customize your instance to your liking and how you can contribute to the project.
 
 ## Troubleshooting
 
diff --git a/benchmarks/load_testing/fetcher.ex b/benchmarks/load_testing/fetcher.ex
index e378c51e7..cdc073b2e 100644
--- a/benchmarks/load_testing/fetcher.ex
+++ b/benchmarks/load_testing/fetcher.ex
@@ -39,10 +39,12 @@ defmodule Pleroma.LoadTesting.Fetcher do
       "muting_user" => user
     }
 
+    following = User.following(user)
+
     Benchee.run(%{
       "User home timeline" => fn ->
         Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
-          [user.ap_id | user.following],
+          following,
           home_timeline_params
         )
       end,
@@ -60,7 +62,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
 
     home_activities =
       Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
-        [user.ap_id | user.following],
+        following,
         home_timeline_params
       )
 
diff --git a/benchmarks/load_testing/generator.ex b/benchmarks/load_testing/generator.ex
index 5c5a5c122..b4432bdb7 100644
--- a/benchmarks/load_testing/generator.ex
+++ b/benchmarks/load_testing/generator.ex
@@ -45,15 +45,13 @@ defmodule Pleroma.LoadTesting.Generator do
         %{
           ap_id: ap_id,
           follower_address: ap_id <> "/followers",
-          following_address: ap_id <> "/following",
-          following: [ap_id]
+          following_address: ap_id <> "/following"
         }
       else
         %{
           ap_id: User.ap_id(user),
           follower_address: User.ap_followers(user),
-          following_address: User.ap_following(user),
-          following: [User.ap_id(user)]
+          following_address: User.ap_following(user)
         }
       end
 
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 8a827ca80..e2b5251bc 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -52,9 +52,9 @@ defmodule Mix.Tasks.Pleroma.Database do
   def run(["update_users_following_followers_counts"]) do
     start_pleroma()
 
-    users = Repo.all(User)
-    Enum.each(users, &User.remove_duplicated_following/1)
-    Enum.each(users, &User.update_follower_count/1)
+    User
+    |> Repo.all()
+    |> Enum.each(&User.update_follower_count/1)
   end
 
   def run(["prune_objects" | args]) do
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index d7bdc2310..4e3b80db3 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -163,7 +163,7 @@ defmodule Mix.Tasks.Pleroma.User do
 
       user = User.get_cached_by_id(user.id)
 
-      if Enum.empty?(user.following) do
+      if Enum.empty?(User.get_friends(user)) do
         shell_info("Successfully unsubscribed all followers from #{user.nickname}")
       end
     else
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
index fa838a4e4..b0e9ebbd0 100644
--- a/lib/pleroma/bbs/handler.ex
+++ b/lib/pleroma/bbs/handler.ex
@@ -97,7 +97,7 @@ defmodule Pleroma.BBS.Handler do
       |> Map.put("user", user)
 
     activities =
-      [user.ap_id | user.following]
+      [user.ap_id | Pleroma.User.following(user)]
       |> ActivityPub.fetch_activities(params)
 
     Enum.each(activities, fn activity ->
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index 098016af2..ade3a526a 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -67,7 +67,13 @@ defmodule Pleroma.Conversation do
 
       participations =
         Enum.map(users, fn user ->
-          User.increment_unread_conversation_count(conversation, user)
+          invisible_conversation = Enum.any?(users, &User.blocks?(user, &1))
+
+          unless invisible_conversation do
+            User.increment_unread_conversation_count(conversation, user)
+          end
+
+          opts = Keyword.put(opts, :invisible_conversation, invisible_conversation)
 
           {:ok, participation} =
             Participation.create_for_user_and_conversation(user, conversation, opts)
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index 41918fa78..176b82a20 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -32,11 +32,20 @@ defmodule Pleroma.Conversation.Participation do
 
   def create_for_user_and_conversation(user, conversation, opts \\ []) do
     read = !!opts[:read]
+    invisible_conversation = !!opts[:invisible_conversation]
+
+    update_on_conflict =
+      if(invisible_conversation, do: [], else: [read: read])
+      |> Keyword.put(:updated_at, NaiveDateTime.utc_now())
 
     %__MODULE__{}
-    |> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})
+    |> creation_cng(%{
+      user_id: user.id,
+      conversation_id: conversation.id,
+      read: invisible_conversation || read
+    })
     |> Repo.insert(
-      on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],
+      on_conflict: [set: update_on_conflict],
       returning: true,
       conflict_target: [:user_id, :conversation_id]
     )
@@ -69,7 +78,26 @@ defmodule Pleroma.Conversation.Participation do
     end
   end
 
-  def mark_all_as_read(user) do
+  def mark_all_as_read(%User{local: true} = user, %User{} = target_user) do
+    target_conversation_ids =
+      __MODULE__
+      |> where([p], p.user_id == ^target_user.id)
+      |> select([p], p.conversation_id)
+      |> Repo.all()
+
+    __MODULE__
+    |> where([p], p.user_id == ^user.id)
+    |> where([p], p.conversation_id in ^target_conversation_ids)
+    |> update([p], set: [read: true])
+    |> Repo.update_all([])
+
+    {:ok, user} = User.set_unread_conversation_count(user)
+    {:ok, user, []}
+  end
+
+  def mark_all_as_read(%User{} = user, %User{}), do: {:ok, user, []}
+
+  def mark_all_as_read(%User{} = user) do
     {_, participations} =
       __MODULE__
       |> where([p], p.user_id == ^user.id)
@@ -78,8 +106,8 @@ defmodule Pleroma.Conversation.Participation do
       |> select([p], p)
       |> Repo.update_all([])
 
-    User.set_unread_conversation_count(user)
-    {:ok, participations}
+    {:ok, user} = User.set_unread_conversation_count(user)
+    {:ok, user, participations}
   end
 
   def mark_as_unread(participation) do
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
new file mode 100644
index 000000000..2ffac17ee
--- /dev/null
+++ b/lib/pleroma/following_relationship.ex
@@ -0,0 +1,110 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.FollowingRelationship do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+  import Ecto.Query
+
+  alias FlakeId.Ecto.CompatType
+  alias Pleroma.Repo
+  alias Pleroma.User
+
+  schema "following_relationships" do
+    field(:state, :string, default: "accept")
+
+    belongs_to(:follower, User, type: CompatType)
+    belongs_to(:following, User, type: CompatType)
+
+    timestamps()
+  end
+
+  def changeset(%__MODULE__{} = following_relationship, attrs) do
+    following_relationship
+    |> cast(attrs, [:state])
+    |> put_assoc(:follower, attrs.follower)
+    |> put_assoc(:following, attrs.following)
+    |> validate_required([:state, :follower, :following])
+  end
+
+  def get(%User{} = follower, %User{} = following) do
+    __MODULE__
+    |> where(follower_id: ^follower.id, following_id: ^following.id)
+    |> Repo.one()
+  end
+
+  def update(follower, following, "reject"), do: unfollow(follower, following)
+
+  def update(%User{} = follower, %User{} = following, state) do
+    case get(follower, following) do
+      nil ->
+        follow(follower, following, state)
+
+      following_relationship ->
+        following_relationship
+        |> cast(%{state: state}, [:state])
+        |> validate_required([:state])
+        |> Repo.update()
+    end
+  end
+
+  def follow(%User{} = follower, %User{} = following, state \\ "accept") do
+    %__MODULE__{}
+    |> changeset(%{follower: follower, following: following, state: state})
+    |> Repo.insert(on_conflict: :nothing)
+  end
+
+  def unfollow(%User{} = follower, %User{} = following) do
+    case get(follower, following) do
+      nil -> {:ok, nil}
+      %__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
+    end
+  end
+
+  def follower_count(%User{} = user) do
+    %{followers: user, deactivated: false}
+    |> User.Query.build()
+    |> Repo.aggregate(:count, :id)
+  end
+
+  def following_count(%User{id: nil}), do: 0
+
+  def following_count(%User{} = user) do
+    %{friends: user, deactivated: false}
+    |> User.Query.build()
+    |> Repo.aggregate(:count, :id)
+  end
+
+  def get_follow_requests(%User{id: id}) do
+    __MODULE__
+    |> join(:inner, [r], f in assoc(r, :follower))
+    |> where([r], r.state == "pending")
+    |> where([r], r.following_id == ^id)
+    |> select([r, f], f)
+    |> Repo.all()
+  end
+
+  def following?(%User{id: follower_id}, %User{id: followed_id}) do
+    __MODULE__
+    |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
+    |> Repo.exists?()
+  end
+
+  def following(%User{} = user) do
+    following =
+      __MODULE__
+      |> join(:inner, [r], u in User, on: r.following_id == u.id)
+      |> where([r], r.follower_id == ^user.id)
+      |> where([r], r.state == "accept")
+      |> select([r, u], u.follower_address)
+      |> Repo.all()
+
+    if not user.local or user.nickname in [nil, "internal.fetch"] do
+      following
+    else
+      [user.follower_address | following]
+    end
+  end
+end
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 7758cb90b..441ae8b65 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -90,6 +90,9 @@ defmodule Pleroma.Object.Fetcher do
       {:fetch_object, %Object{} = object} ->
         {:ok, object}
 
+      {:fetch, {:error, error}} ->
+        {:error, error}
+
       e ->
         e
     end
@@ -110,6 +113,9 @@ defmodule Pleroma.Object.Fetcher do
     with {:ok, object} <- fetch_object_from_id(id, options) do
       object
     else
+      {:error, %Tesla.Mock.Error{}} ->
+        nil
+
       e ->
         Logger.error("Error while fetching #{id}: #{inspect(e)}")
         nil
@@ -170,6 +176,9 @@ defmodule Pleroma.Object.Fetcher do
       {:scheme, _} ->
         {:error, "Unsupported URI scheme"}
 
+      {:error, e} ->
+        {:error, e}
+
       e ->
         {:error, e}
     end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 7bef6e281..40171620e 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.User do
   alias Pleroma.Activity
   alias Pleroma.Conversation.Participation
   alias Pleroma.Delivery
+  alias Pleroma.FollowingRelationship
   alias Pleroma.Keys
   alias Pleroma.Notification
   alias Pleroma.Object
@@ -50,7 +51,6 @@ defmodule Pleroma.User do
     field(:password, :string, virtual: true)
     field(:password_confirmation, :string, virtual: true)
     field(:keys, :string)
-    field(:following, {:array, :string}, default: [])
     field(:ap_id, :string)
     field(:avatar, :map)
     field(:local, :boolean, default: true)
@@ -216,61 +216,7 @@ defmodule Pleroma.User do
     from(u in query, where: u.deactivated != ^true)
   end
 
-  def following_count(%User{following: []}), do: 0
-
-  def following_count(%User{} = user) do
-    user
-    |> get_friends_query()
-    |> Repo.aggregate(:count, :id)
-  end
-
-  @info_fields [
-    :banner,
-    :background,
-    :source_data,
-    :note_count,
-    :follower_count,
-    :following_count,
-    :locked,
-    :confirmation_pending,
-    :password_reset_pending,
-    :confirmation_token,
-    :default_scope,
-    :blocks,
-    :domain_blocks,
-    :mutes,
-    :muted_reblogs,
-    :muted_notifications,
-    :subscribers,
-    :deactivated,
-    :no_rich_text,
-    :ap_enabled,
-    :is_moderator,
-    :is_admin,
-    :show_role,
-    :settings,
-    :magic_key,
-    :uri,
-    :hide_followers_count,
-    :hide_follows_count,
-    :hide_followers,
-    :hide_follows,
-    :hide_favorites,
-    :unread_conversation_count,
-    :pinned_activities,
-    :email_notifications,
-    :mascot,
-    :emoji,
-    :pleroma_settings_store,
-    :fields,
-    :raw_fields,
-    :discoverable,
-    :invisible,
-    :skip_thread_containment,
-    :notification_settings
-  ]
-
-  def info_fields, do: @info_fields
+  defdelegate following_count(user), to: FollowingRelationship
 
   defp truncate_fields_param(params) do
     if Map.has_key?(params, :fields) do
@@ -357,7 +303,6 @@ defmodule Pleroma.User do
         :bio,
         :name,
         :avatar,
-        :following,
         :locked,
         :no_rich_text,
         :default_scope,
@@ -502,7 +447,6 @@ defmodule Pleroma.User do
     followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
 
     changeset
-    |> put_change(:following, [followers])
     |> put_change(:follower_address, followers)
   end
 
@@ -556,8 +500,8 @@ defmodule Pleroma.User do
   def needs_update?(_), do: true
 
   @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
-  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
-    {:ok, follower}
+  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
+    follow(follower, followed, "pending")
   end
 
   def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
@@ -575,37 +519,22 @@ defmodule Pleroma.User do
   @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
   @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
   def follow_all(follower, followeds) do
-    followed_addresses =
-      followeds
-      |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
-      |> Enum.map(fn %{follower_address: fa} -> fa end)
+    followeds =
+      Enum.reject(followeds, fn followed ->
+        blocks?(follower, followed) || blocks?(followed, follower)
+      end)
 
-    q =
-      from(u in User,
-        where: u.id == ^follower.id,
-        update: [
-          set: [
-            following:
-              fragment(
-                "array(select distinct unnest (array_cat(?, ?)))",
-                u.following,
-                ^followed_addresses
-              )
-          ]
-        ],
-        select: u
-      )
-
-    {1, [follower]} = Repo.update_all(q, [])
+    Enum.each(followeds, &follow(follower, &1, "accept"))
 
     Enum.each(followeds, &update_follower_count/1)
 
     set_cache(follower)
   end
 
-  def follow(%User{} = follower, %User{} = followed) do
+  defdelegate following(user), to: FollowingRelationship
+
+  def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
     deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
-    ap_followers = followed.follower_address
 
     cond do
       followed.deactivated ->
@@ -615,14 +544,7 @@ defmodule Pleroma.User do
         {:error, "Could not follow user: #{followed.nickname} blocked you."}
 
       true ->
-        q =
-          from(u in User,
-            where: u.id == ^follower.id,
-            update: [push: [following: ^ap_followers]],
-            select: u
-          )
-
-        {1, [follower]} = Repo.update_all(q, [])
+        FollowingRelationship.follow(follower, followed, state)
 
         follower = maybe_update_following_count(follower)
 
@@ -633,17 +555,8 @@ defmodule Pleroma.User do
   end
 
   def unfollow(%User{} = follower, %User{} = followed) do
-    ap_followers = followed.follower_address
-
     if following?(follower, followed) and follower.ap_id != followed.ap_id do
-      q =
-        from(u in User,
-          where: u.id == ^follower.id,
-          update: [pull: [following: ^ap_followers]],
-          select: u
-        )
-
-      {1, [follower]} = Repo.update_all(q, [])
+      FollowingRelationship.unfollow(follower, followed)
 
       follower = maybe_update_following_count(follower)
 
@@ -657,10 +570,7 @@ defmodule Pleroma.User do
     end
   end
 
-  @spec following?(User.t(), User.t()) :: boolean
-  def following?(%User{} = follower, %User{} = followed) do
-    Enum.member?(follower.following, followed.follower_address)
-  end
+  defdelegate following?(follower, followed), to: FollowingRelationship
 
   def locked?(%User{} = user) do
     user.locked || false
@@ -882,16 +792,7 @@ defmodule Pleroma.User do
     |> Repo.all()
   end
 
-  @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
-  def get_follow_requests(%User{} = user) do
-    user
-    |> Activity.follow_requests_for_actor()
-    |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
-    |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
-    |> group_by([a, u], u.id)
-    |> select([a, u], u)
-    |> Repo.all()
-  end
+  defdelegate get_follow_requests(user), to: FollowingRelationship
 
   def increase_note_count(%User{} = user) do
     User
@@ -1019,7 +920,7 @@ defmodule Pleroma.User do
     end
   end
 
-  def set_unread_conversation_count(_), do: :noop
+  def set_unread_conversation_count(user), do: {:ok, user}
 
   def increment_unread_conversation_count(conversation, %User{local: true} = user) do
     unread_query =
@@ -1041,19 +942,7 @@ defmodule Pleroma.User do
     end
   end
 
-  def increment_unread_conversation_count(_, _), do: :noop
-
-  def remove_duplicated_following(%User{following: following} = user) do
-    uniq_following = Enum.uniq(following)
-
-    if length(following) == length(uniq_following) do
-      {:ok, user}
-    else
-      user
-      |> update_changeset(%{following: uniq_following})
-      |> update_and_set_cache()
-    end
-  end
+  def increment_unread_conversation_count(_, user), do: {:ok, user}
 
   @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
   def get_users_from_set(ap_ids, local_only \\ true) do
@@ -1125,7 +1014,7 @@ defmodule Pleroma.User do
     if following?(blocked, blocker), do: unfollow(blocked, blocker)
 
     {:ok, blocker} = update_follower_count(blocker)
-
+    {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
     add_to_block(blocker, ap_id)
   end
 
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 7f5273c4e..2eda454bc 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -28,6 +28,8 @@ defmodule Pleroma.User.Query do
   """
   import Ecto.Query
   import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
+
+  alias Pleroma.FollowingRelationship
   alias Pleroma.User
 
   @type criteria ::
@@ -139,18 +141,40 @@ defmodule Pleroma.User.Query do
     |> where([u], not is_nil(u.nickname))
   end
 
-  defp compose_query({:followers, %User{id: id, follower_address: follower_address}}, query) do
-    where(query, [u], fragment("? <@ ?", ^[follower_address], u.following))
+  defp compose_query({:followers, %User{id: id}}, query) do
+    query
     |> where([u], u.id != ^id)
+    |> join(:inner, [u], r in FollowingRelationship,
+      as: :relationships,
+      on: r.following_id == ^id and r.follower_id == u.id
+    )
+    |> where([relationships: r], r.state == "accept")
   end
 
-  defp compose_query({:friends, %User{id: id, following: following}}, query) do
-    where(query, [u], u.follower_address in ^following)
+  defp compose_query({:friends, %User{id: id}}, query) do
+    query
     |> where([u], u.id != ^id)
+    |> join(:inner, [u], r in FollowingRelationship,
+      as: :relationships,
+      on: r.following_id == u.id and r.follower_id == ^id
+    )
+    |> where([relationships: r], r.state == "accept")
   end
 
   defp compose_query({:recipients_from_activity, to}, query) do
-    where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
+    query
+    |> join(:left, [u], r in FollowingRelationship,
+      as: :relationships,
+      on: r.follower_id == u.id
+    )
+    |> join(:left, [relationships: r], f in User,
+      as: :following,
+      on: f.id == r.following_id
+    )
+    |> where(
+      [u, following: f, relationships: r],
+      u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
+    )
   end
 
   defp compose_query({:order_by, key}, query) do
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 07dde3537..093ee8fcd 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -518,7 +518,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     public = [Pleroma.Constants.as_public()]
 
     recipients =
-      if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
+      if opts["user"],
+        do: [opts["user"].ap_id | User.following(opts["user"])] ++ public,
+        else: public
 
     from(activity in Activity)
     |> maybe_preload_objects(opts)
@@ -712,7 +714,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp user_activities_recipients(%{"reading_user" => reading_user}) do
     if reading_user do
-      [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
+      [Pleroma.Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)]
     else
       [Pleroma.Constants.as_public()]
     end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 568623318..b2cd965fe 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -319,12 +319,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
       when page? in [true, "true"] do
     activities =
       if params["max_id"] do
-        ActivityPub.fetch_activities([user.ap_id | user.following], %{
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{
           "max_id" => params["max_id"],
           "limit" => 10
         })
       else
-        ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], %{"limit" => 10})
       end
 
     conn
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index a9434d75c..f90d75a8a 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -57,9 +57,10 @@ defmodule Pleroma.Web.ActivityPub.Relay do
 
   @spec list() :: {:ok, [String.t()]} | {:error, any()}
   def list do
-    with %User{following: following} = _user <- get_actor() do
+    with %User{} = user <- get_actor() do
       list =
-        following
+        user
+        |> User.following()
         |> Enum.map(fn entry -> URI.parse(entry).host end)
         |> Enum.uniq()
 
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 9b3ee842b..91a164eff 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   A module to handle coding from internal to wire ActivityPub and back.
   """
   alias Pleroma.Activity
+  alias Pleroma.FollowingRelationship
   alias Pleroma.Object
   alias Pleroma.Object.Containment
   alias Pleroma.Repo
@@ -474,7 +475,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
            {_, false} <- {:user_locked, User.locked?(followed)},
            {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
            {_, {:ok, _}} <-
-             {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")} do
+             {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
+           {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
         ActivityPub.accept(%{
           to: [follower.ap_id],
           actor: followed,
@@ -484,6 +486,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       else
         {:user_blocked, true} ->
           {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
 
           ActivityPub.reject(%{
             to: [follower.ap_id],
@@ -494,6 +497,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
         {:follow, {:error, _}} ->
           {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
 
           ActivityPub.reject(%{
             to: [follower.ap_id],
@@ -503,6 +507,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           })
 
         {:user_locked, true} ->
+          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
           :noop
       end
 
@@ -522,7 +527,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
-         {:ok, _follower} = User.follow(follower, followed) do
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
       ActivityPub.accept(%{
         to: follow_activity.data["to"],
         type: "Accept",
@@ -545,6 +550,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
          %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
          {:ok, activity} <-
            ActivityPub.reject(%{
              to: follow_activity.data["to"],
@@ -554,8 +560,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
              local: false,
              activity_id: id
            }) do
-      User.unfollow(follower, followed)
-
       {:ok, activity}
     else
       _e -> :error
@@ -1061,43 +1065,22 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     # we pass a fake user so that the followers collection is stripped away
     old_follower_address = User.ap_followers(%User{nickname: user.nickname})
 
-    q =
-      from(
-        u in User,
-        where: ^old_follower_address in u.following,
-        update: [
-          set: [
-            following:
-              fragment(
-                "array_replace(?,?,?)",
-                u.following,
-                ^old_follower_address,
-                ^user.follower_address
-              )
-          ]
+    from(
+      a in Activity,
+      where: ^old_follower_address in a.recipients,
+      update: [
+        set: [
+          recipients:
+            fragment(
+              "array_replace(?,?,?)",
+              a.recipients,
+              ^old_follower_address,
+              ^user.follower_address
+            )
         ]
-      )
-
-    Repo.update_all(q, [])
-
-    q =
-      from(
-        a in Activity,
-        where: ^old_follower_address in a.recipients,
-        update: [
-          set: [
-            recipients:
-              fragment(
-                "array_replace(?,?,?)",
-                a.recipients,
-                ^old_follower_address,
-                ^user.follower_address
-              )
-          ]
-        ]
-      )
-
-    Repo.update_all(q, [])
+      ]
+    )
+    |> Repo.update_all([])
   end
 
   def upgrade_user_from_ap_id(ap_id) do
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index f3ab48f7c..cd4097493 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -59,7 +59,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   end
 
   def visible_for_user?(activity, user) do
-    x = [user.ap_id | user.following]
+    x = [user.ap_id | User.following(user)]
     y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
     visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
   end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 449b808b5..e57345621 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.Activity
   alias Pleroma.ActivityExpiration
   alias Pleroma.Conversation.Participation
+  alias Pleroma.FollowingRelationship
   alias Pleroma.Object
   alias Pleroma.ThreadMute
   alias Pleroma.User
@@ -40,6 +41,7 @@ defmodule Pleroma.Web.CommonAPI do
     with {:ok, follower} <- User.follow(follower, followed),
          %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
          {:ok, _activity} <-
            ActivityPub.accept(%{
              to: [follower.ap_id],
@@ -54,6 +56,7 @@ defmodule Pleroma.Web.CommonAPI do
   def reject_follow_request(follower, followed) do
     with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
          {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
+         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
          {:ok, _activity} <-
            ActivityPub.reject(%{
              to: [follower.ap_id],
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 9f086a8c2..f2d2d3ccb 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
   alias Pleroma.Pagination
   alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
 
   plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
@@ -28,7 +29,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
       |> Map.put("muting_user", user)
       |> Map.put("user", user)
 
-    recipients = [user.ap_id | user.following]
+    recipients = [user.ap_id | User.following(user)]
 
     activities =
       recipients
@@ -128,9 +129,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
 
       # we must filter the following list for the user to avoid leaking statuses the user
       # does not actually have permission to see (for more info, peruse security issue #270).
+
+      user_following = User.following(user)
+
       activities =
         following
-        |> Enum.filter(fn x -> x in user.following end)
+        |> Enum.filter(fn x -> x in user_following end)
         |> ActivityPub.fetch_activities_bounded(following, params)
         |> Enum.reverse()
 
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index ee40bbf33..db6faac83 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -126,7 +126,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
 
     recipients =
       if for_user do
-        [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
+        [Pleroma.Constants.as_public()] ++ [for_user.ap_id | User.following(for_user)]
       else
         [Pleroma.Constants.as_public()]
       end
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index fc39abf05..651a99423 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -80,7 +80,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
   end
 
   def read_conversations(%{assigns: %{user: user}} = conn, _params) do
-    with {:ok, participations} <- Participation.mark_all_as_read(user) do
+    with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
       conn
       |> add_link_headers(participations)
       |> put_view(ConversationView)
diff --git a/priv/repo/migrations/20191007073319_create_following_relationships.exs b/priv/repo/migrations/20191007073319_create_following_relationships.exs
new file mode 100644
index 000000000..d49e24ee4
--- /dev/null
+++ b/priv/repo/migrations/20191007073319_create_following_relationships.exs
@@ -0,0 +1,149 @@
+defmodule Pleroma.Repo.Migrations.CreateFollowingRelationships do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:following_relationships) do
+      add(:follower_id, references(:users, type: :uuid, on_delete: :delete_all), null: false)
+      add(:following_id, references(:users, type: :uuid, on_delete: :delete_all), null: false)
+      add(:state, :string, null: false)
+
+      timestamps()
+    end
+
+    create_if_not_exists(index(:following_relationships, :follower_id))
+    create_if_not_exists(unique_index(:following_relationships, [:follower_id, :following_id]))
+
+    execute(update_thread_visibility(), restore_thread_visibility())
+  end
+
+  # The only difference between the original version: `actor_user` replaced with `actor_user_following`
+  def update_thread_visibility do
+    """
+    CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
+    DECLARE
+      public varchar := 'https://www.w3.org/ns/activitystreams#Public';
+      child objects%ROWTYPE;
+      activity activities%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 activity.
+      SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
+
+      LOOP
+        --- Ensure that we have an activity before continuing.
+        --- If we don't, the thread is not satisfiable.
+        IF activity IS NULL THEN
+          RETURN false;
+        END IF;
+
+        --- We only care about Create activities.
+        IF activity.data->>'type' != 'Create' THEN
+          RETURN true;
+        END IF;
+
+        --- Normalize the child object into child.
+        SELECT * INTO child FROM objects
+        INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+        WHERE COALESCE(activity.data->'object'->>'id', activity.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 = activity.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 && activity.recipients THEN
+          --- activity 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 activity FROM activities
+          INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+          WHERE child.data->>'inReplyTo' = objects.data->>'id';
+        ELSE
+          RETURN true;
+        END IF;
+      END LOOP;
+    END;
+    $$ LANGUAGE plpgsql IMMUTABLE;
+    """
+  end
+
+  # priv/repo/migrations/20190515222404_add_thread_visibility_function.exs
+  def restore_thread_visibility do
+    """
+    CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
+    DECLARE
+      public varchar := 'https://www.w3.org/ns/activitystreams#Public';
+      child objects%ROWTYPE;
+      activity activities%ROWTYPE;
+      actor_user users%ROWTYPE;
+      author_fa varchar;
+      valid_recipients varchar[];
+    BEGIN
+      --- Fetch our actor.
+      SELECT * INTO actor_user FROM users WHERE users.ap_id = actor;
+
+      --- Fetch our initial activity.
+      SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
+
+      LOOP
+        --- Ensure that we have an activity before continuing.
+        --- If we don't, the thread is not satisfiable.
+        IF activity IS NULL THEN
+          RETURN false;
+        END IF;
+
+        --- We only care about Create activities.
+        IF activity.data->>'type' != 'Create' THEN
+          RETURN true;
+        END IF;
+
+        --- Normalize the child object into child.
+        SELECT * INTO child FROM objects
+        INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
+        WHERE COALESCE(activity.data->'object'->>'id', activity.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 = activity.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 && activity.recipients THEN
+          --- activity 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 activity FROM activities
+          INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.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
diff --git a/priv/repo/migrations/20191008132217_migrate_following_relationships.exs b/priv/repo/migrations/20191008132217_migrate_following_relationships.exs
new file mode 100644
index 000000000..9d5c2648f
--- /dev/null
+++ b/priv/repo/migrations/20191008132217_migrate_following_relationships.exs
@@ -0,0 +1,89 @@
+defmodule Pleroma.Repo.Migrations.MigrateFollowingRelationships do
+  use Ecto.Migration
+
+  def change do
+    execute(import_following_from_users(), "")
+    execute(import_following_from_activities(), restore_following_column())
+  end
+
+  defp import_following_from_users do
+    """
+    INSERT INTO following_relationships (follower_id, following_id, state, inserted_at, updated_at)
+    SELECT
+        relations.follower_id,
+        following.id,
+        'accept',
+        now(),
+        now()
+    FROM (
+        SELECT
+            users.id AS follower_id,
+            unnest(users.following) AS following_ap_id
+        FROM
+            users
+        WHERE
+            users.following != '{}'
+            AND users.local = false OR users.local = true AND users.email IS NOT NULL -- Exclude `internal/fetch` and `relay`
+    ) AS relations
+        JOIN users AS "following" ON "following".follower_address = relations.following_ap_id
+
+        WHERE relations.follower_id != following.id
+    ON CONFLICT DO NOTHING
+    """
+  end
+
+  defp import_following_from_activities do
+    """
+    INSERT INTO
+        following_relationships (
+            follower_id,
+            following_id,
+            state,
+            inserted_at,
+            updated_at
+        )
+    SELECT
+        followers.id,
+        following.id,
+        activities.data ->> 'state',
+        (activities.data ->> 'published') :: timestamp,
+        now()
+    FROM
+        activities
+        JOIN users AS followers ON (activities.actor = followers.ap_id)
+        JOIN users AS following ON (activities.data ->> 'object' = following.ap_id)
+    WHERE
+        activities.data ->> 'type' = 'Follow'
+        AND activities.data ->> 'state' IN ('accept', 'pending', 'reject')
+    ORDER BY activities.updated_at DESC
+    ON CONFLICT DO NOTHING
+    """
+  end
+
+  defp restore_following_column do
+    """
+    UPDATE
+        users
+    SET
+        following = following_query.following_array,
+        updated_at = now()
+    FROM (
+        SELECT
+            follower.id AS follower_id,
+            CASE follower.local
+            WHEN TRUE THEN
+                array_prepend(follower.follower_address, array_agg(following.follower_address))
+            ELSE
+                array_agg(following.follower_address)
+            END AS following_array
+        FROM
+            following_relationships
+            JOIN users AS follower ON follower.id = following_relationships.follower_id
+            JOIN users AS following ON following.id = following_relationships.following_id
+        GROUP BY
+            follower.id) AS following_query
+    WHERE
+        following_query.follower_id = users.id
+    """
+  end
+end
diff --git a/priv/repo/migrations/20191008132427_drop_users_following.exs b/priv/repo/migrations/20191008132427_drop_users_following.exs
new file mode 100644
index 000000000..21c0af9f4
--- /dev/null
+++ b/priv/repo/migrations/20191008132427_drop_users_following.exs
@@ -0,0 +1,16 @@
+defmodule Pleroma.Repo.Migrations.DropUsersFollowing do
+  use Ecto.Migration
+
+  # had to disable these to be able to restore `following` index concurrently
+  # https://hexdocs.pm/ecto_sql/Ecto.Migration.html#index/3-adding-dropping-indexes-concurrently
+  @disable_ddl_transaction true
+  @disable_migration_lock true
+
+  def change do
+    drop(index(:users, [:following], concurrently: true, using: :gin))
+
+    alter table(:users) do
+      remove(:following, {:array, :string}, default: [])
+    end
+  end
+end
diff --git a/priv/repo/migrations/20191009154606_add_user_info_columns.exs b/priv/repo/migrations/20191009154606_add_user_info_columns.exs
new file mode 100644
index 000000000..22a5a377f
--- /dev/null
+++ b/priv/repo/migrations/20191009154606_add_user_info_columns.exs
@@ -0,0 +1,53 @@
+defmodule Pleroma.Repo.Migrations.AddUsersInfoColumns do
+  use Ecto.Migration
+
+  @jsonb_array_default "'[]'::jsonb"
+
+  def change do
+    alter table(:users) do
+      add_if_not_exists(:banner, :map, default: %{})
+      add_if_not_exists(:background, :map, default: %{})
+      add_if_not_exists(:source_data, :map, default: %{})
+      add_if_not_exists(:note_count, :integer, default: 0)
+      add_if_not_exists(:follower_count, :integer, default: 0)
+      add_if_not_exists(:following_count, :integer, default: nil)
+      add_if_not_exists(:locked, :boolean, default: false, null: false)
+      add_if_not_exists(:confirmation_pending, :boolean, default: false, null: false)
+      add_if_not_exists(:password_reset_pending, :boolean, default: false, null: false)
+      add_if_not_exists(:confirmation_token, :text, default: nil)
+      add_if_not_exists(:default_scope, :string, default: "public")
+      add_if_not_exists(:blocks, {:array, :text}, default: [])
+      add_if_not_exists(:domain_blocks, {:array, :text}, default: [])
+      add_if_not_exists(:mutes, {:array, :text}, default: [])
+      add_if_not_exists(:muted_reblogs, {:array, :text}, default: [])
+      add_if_not_exists(:muted_notifications, {:array, :text}, default: [])
+      add_if_not_exists(:subscribers, {:array, :text}, default: [])
+      add_if_not_exists(:deactivated, :boolean, default: false, null: false)
+      add_if_not_exists(:no_rich_text, :boolean, default: false, null: false)
+      add_if_not_exists(:ap_enabled, :boolean, default: false, null: false)
+      add_if_not_exists(:is_moderator, :boolean, default: false, null: false)
+      add_if_not_exists(:is_admin, :boolean, default: false, null: false)
+      add_if_not_exists(:show_role, :boolean, default: true, null: false)
+      add_if_not_exists(:settings, :map, default: nil)
+      add_if_not_exists(:magic_key, :text, default: nil)
+      add_if_not_exists(:uri, :text, default: nil)
+      add_if_not_exists(:hide_followers_count, :boolean, default: false, null: false)
+      add_if_not_exists(:hide_follows_count, :boolean, default: false, null: false)
+      add_if_not_exists(:hide_followers, :boolean, default: false, null: false)
+      add_if_not_exists(:hide_follows, :boolean, default: false, null: false)
+      add_if_not_exists(:hide_favorites, :boolean, default: true, null: false)
+      add_if_not_exists(:unread_conversation_count, :integer, default: 0)
+      add_if_not_exists(:pinned_activities, {:array, :text}, default: [])
+      add_if_not_exists(:email_notifications, :map, default: %{"digest" => false})
+      add_if_not_exists(:mascot, :map, default: nil)
+      add_if_not_exists(:emoji, :map, default: fragment(@jsonb_array_default))
+      add_if_not_exists(:pleroma_settings_store, :map, default: %{})
+      add_if_not_exists(:fields, :map, default: fragment(@jsonb_array_default))
+      add_if_not_exists(:raw_fields, :map, default: fragment(@jsonb_array_default))
+      add_if_not_exists(:discoverable, :boolean, default: false, null: false)
+      add_if_not_exists(:invisible, :boolean, default: false, null: false)
+      add_if_not_exists(:notification_settings, :map, default: %{})
+      add_if_not_exists(:skip_thread_containment, :boolean, default: false, null: false)
+    end
+  end
+end
diff --git a/priv/repo/migrations/20191009154608_copy_users_info_fields_to_users.exs b/priv/repo/migrations/20191009154608_copy_users_info_fields_to_users.exs
index 9dd27511c..cc5ae6920 100644
--- a/priv/repo/migrations/20191009154608_copy_users_info_fields_to_users.exs
+++ b/priv/repo/migrations/20191009154608_copy_users_info_fields_to_users.exs
@@ -95,79 +95,37 @@ defmodule Pleroma.Repo.Migrations.CopyUsersInfoFieldsToUsers do
   ]
 
   def change do
-    alter table(:users) do
-      add(:banner, :map, default: %{})
-      add(:background, :map, default: %{})
-      add(:source_data, :map, default: %{})
-      add(:note_count, :integer, default: 0)
-      add(:follower_count, :integer, default: 0)
-      add(:following_count, :integer, default: nil)
-      add(:locked, :boolean, default: false, null: false)
-      add(:confirmation_pending, :boolean, default: false, null: false)
-      add(:password_reset_pending, :boolean, default: false, null: false)
-      add(:confirmation_token, :text, default: nil)
-      add(:default_scope, :string, default: "public")
-      add(:blocks, {:array, :text}, default: [])
-      add(:domain_blocks, {:array, :text}, default: [])
-      add(:mutes, {:array, :text}, default: [])
-      add(:muted_reblogs, {:array, :text}, default: [])
-      add(:muted_notifications, {:array, :text}, default: [])
-      add(:subscribers, {:array, :text}, default: [])
-      add(:deactivated, :boolean, default: false, null: false)
-      add(:no_rich_text, :boolean, default: false, null: false)
-      add(:ap_enabled, :boolean, default: false, null: false)
-      add(:is_moderator, :boolean, default: false, null: false)
-      add(:is_admin, :boolean, default: false, null: false)
-      add(:show_role, :boolean, default: true, null: false)
-      add(:settings, :map, default: nil)
-      add(:magic_key, :text, default: nil)
-      add(:uri, :text, default: nil)
-      add(:hide_followers_count, :boolean, default: false, null: false)
-      add(:hide_follows_count, :boolean, default: false, null: false)
-      add(:hide_followers, :boolean, default: false, null: false)
-      add(:hide_follows, :boolean, default: false, null: false)
-      add(:hide_favorites, :boolean, default: true, null: false)
-      add(:unread_conversation_count, :integer, default: 0)
-      add(:pinned_activities, {:array, :text}, default: [])
-      add(:email_notifications, :map, default: %{"digest" => false})
-      add(:mascot, :map, default: nil)
-      add(:emoji, :map, default: fragment(@jsonb_array_default))
-      add(:pleroma_settings_store, :map, default: %{})
-      add(:fields, :map, default: fragment(@jsonb_array_default))
-      add(:raw_fields, :map, default: fragment(@jsonb_array_default))
-      add(:discoverable, :boolean, default: false, null: false)
-      add(:invisible, :boolean, default: false, null: false)
-      add(:notification_settings, :map, default: %{})
-      add(:skip_thread_containment, :boolean, default: false, null: false)
-    end
-
     if direction() == :up do
-      for f <- @info_fields do
-        set_field = "update users set #{f} ="
+      sets =
+        for f <- @info_fields do
+          set_field = "#{f} ="
 
-        # Coercion of null::jsonb to NULL
-        jsonb = "case when info->>'#{f}' IS NULL then null else info->'#{f}' end"
+          # Coercion of null::jsonb to NULL
+          jsonb = "case when info->>'#{f}' IS NULL then null else info->'#{f}' end"
 
-        cond do
-          f in @jsonb_fields ->
-            execute("#{set_field} #{jsonb}")
+          cond do
+            f in @jsonb_fields ->
+              "#{set_field} #{jsonb}"
 
-          f in @array_jsonb_fields ->
-            execute("#{set_field} coalesce(#{jsonb}, #{@jsonb_array_default})")
+            f in @array_jsonb_fields ->
+              "#{set_field} coalesce(#{jsonb}, #{@jsonb_array_default})"
 
-          f in @int_fields ->
-            execute("#{set_field} (info->>'#{f}')::int")
+            f in @int_fields ->
+              "#{set_field} (info->>'#{f}')::int"
 
-          f in @boolean_fields ->
-            execute("#{set_field} coalesce((info->>'#{f}')::boolean, false)")
+            f in @boolean_fields ->
+              "#{set_field} coalesce((info->>'#{f}')::boolean, false)"
 
-          f in @array_text_fields ->
-            execute("#{set_field} ARRAY(SELECT jsonb_array_elements_text(#{jsonb}))")
+            f in @array_text_fields ->
+              "#{set_field} ARRAY(SELECT jsonb_array_elements_text(#{jsonb}))"
 
-          true ->
-            execute("#{set_field} info->>'#{f}'")
+            true ->
+              "#{set_field} info->>'#{f}'"
+          end
         end
-      end
+        |> Enum.join(", ")
+
+      execute("update users set " <> sets)
 
       for index_name <- [
             :users_deactivated_index,
diff --git a/priv/repo/migrations/20191026190317_set_not_null_for_activities.exs b/priv/repo/migrations/20191026190317_set_not_null_for_activities.exs
new file mode 100644
index 000000000..9b66f3cff
--- /dev/null
+++ b/priv/repo/migrations/20191026190317_set_not_null_for_activities.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForActivities do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE activities
+    ALTER COLUMN data SET NOT NULL,
+    ALTER COLUMN local SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE activities
+    ALTER COLUMN data DROP NOT NULL,
+    ALTER COLUMN local DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026190415_set_not_null_for_activity_expirations.exs b/priv/repo/migrations/20191026190415_set_not_null_for_activity_expirations.exs
new file mode 100644
index 000000000..e41c69e4c
--- /dev/null
+++ b/priv/repo/migrations/20191026190415_set_not_null_for_activity_expirations.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForActivityExpirations do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE activity_expirations
+    ALTER COLUMN activity_id SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE activity_expirations
+    ALTER COLUMN activity_id DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026190500_set_not_null_for_apps.exs b/priv/repo/migrations/20191026190500_set_not_null_for_apps.exs
new file mode 100644
index 000000000..a6a44ddfe
--- /dev/null
+++ b/priv/repo/migrations/20191026190500_set_not_null_for_apps.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForApps do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE apps
+    ALTER COLUMN client_name SET NOT NULL,
+    ALTER COLUMN redirect_uris SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE apps
+    ALTER COLUMN client_name DROP NOT NULL,
+    ALTER COLUMN redirect_uris DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026190533_set_not_null_for_bookmarks.exs b/priv/repo/migrations/20191026190533_set_not_null_for_bookmarks.exs
new file mode 100644
index 000000000..5f3224003
--- /dev/null
+++ b/priv/repo/migrations/20191026190533_set_not_null_for_bookmarks.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForBookmarks do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE bookmarks
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN activity_id SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE bookmarks
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN activity_id DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026190622_set_not_null_for_config.exs b/priv/repo/migrations/20191026190622_set_not_null_for_config.exs
new file mode 100644
index 000000000..204272442
--- /dev/null
+++ b/priv/repo/migrations/20191026190622_set_not_null_for_config.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForConfig do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE config
+    ALTER COLUMN key SET NOT NULL,
+    ALTER COLUMN value SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE config
+    ALTER COLUMN key DROP NOT NULL,
+    ALTER COLUMN value DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026190712_set_not_null_for_conversation_participation_recipient_ships.exs b/priv/repo/migrations/20191026190712_set_not_null_for_conversation_participation_recipient_ships.exs
new file mode 100644
index 000000000..a5ab1d3c9
--- /dev/null
+++ b/priv/repo/migrations/20191026190712_set_not_null_for_conversation_participation_recipient_ships.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForConversationParticipationRecipientShips do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE conversation_participation_recipient_ships
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN participation_id SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE conversation_participation_recipient_ships
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN participation_id DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026190759_set_not_null_for_conversation_participations.exs b/priv/repo/migrations/20191026190759_set_not_null_for_conversation_participations.exs
new file mode 100644
index 000000000..cabb1f29f
--- /dev/null
+++ b/priv/repo/migrations/20191026190759_set_not_null_for_conversation_participations.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForConversationParticipations do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE conversation_participations
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN conversation_id SET NOT NULL,
+    ALTER COLUMN read SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE conversation_participations
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN conversation_id DROP NOT NULL,
+    ALTER COLUMN read DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026190841_set_not_null_for_filters.exs b/priv/repo/migrations/20191026190841_set_not_null_for_filters.exs
new file mode 100644
index 000000000..52d7e687a
--- /dev/null
+++ b/priv/repo/migrations/20191026190841_set_not_null_for_filters.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForFilters do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE filters
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN filter_id SET NOT NULL,
+    ALTER COLUMN whole_word SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE filters
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN filter_id DROP NOT NULL,
+    ALTER COLUMN whole_word DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191023_set_not_null_for_instances.exs b/priv/repo/migrations/20191026191023_set_not_null_for_instances.exs
new file mode 100644
index 000000000..4c2560da5
--- /dev/null
+++ b/priv/repo/migrations/20191026191023_set_not_null_for_instances.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForInstances do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE instances
+    ALTER COLUMN host SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE instances
+    ALTER COLUMN host DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191100_set_not_null_for_lists.exs b/priv/repo/migrations/20191026191100_set_not_null_for_lists.exs
new file mode 100644
index 000000000..40b8b136a
--- /dev/null
+++ b/priv/repo/migrations/20191026191100_set_not_null_for_lists.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForLists do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE lists
+    ALTER COLUMN user_id SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE lists
+    ALTER COLUMN user_id DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191134_set_not_null_for_markers.exs b/priv/repo/migrations/20191026191134_set_not_null_for_markers.exs
new file mode 100644
index 000000000..7d7b73e7b
--- /dev/null
+++ b/priv/repo/migrations/20191026191134_set_not_null_for_markers.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForMarkers do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE markers
+    ALTER COLUMN user_id SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE markers
+    ALTER COLUMN user_id DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191218_set_not_null_for_moderation_log.exs b/priv/repo/migrations/20191026191218_set_not_null_for_moderation_log.exs
new file mode 100644
index 000000000..7238ca7f5
--- /dev/null
+++ b/priv/repo/migrations/20191026191218_set_not_null_for_moderation_log.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForModerationLog do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE moderation_log
+    ALTER COLUMN data SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE moderation_log
+    ALTER COLUMN data DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191249_set_not_null_for_notifications.exs b/priv/repo/migrations/20191026191249_set_not_null_for_notifications.exs
new file mode 100644
index 000000000..7e2976bab
--- /dev/null
+++ b/priv/repo/migrations/20191026191249_set_not_null_for_notifications.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForNotifications do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE notifications
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN seen SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE notifications
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN seen DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191328_set_not_null_for_oauth_authorizations.exs b/priv/repo/migrations/20191026191328_set_not_null_for_oauth_authorizations.exs
new file mode 100644
index 000000000..bc6b36e69
--- /dev/null
+++ b/priv/repo/migrations/20191026191328_set_not_null_for_oauth_authorizations.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForOauthAuthorizations do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE oauth_authorizations
+    ALTER COLUMN app_id SET NOT NULL,
+    ALTER COLUMN token SET NOT NULL,
+    ALTER COLUMN used SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE oauth_authorizations
+    ALTER COLUMN app_id DROP NOT NULL,
+    ALTER COLUMN token DROP NOT NULL,
+    ALTER COLUMN used DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191401_set_not_null_for_oauth_tokens.exs b/priv/repo/migrations/20191026191401_set_not_null_for_oauth_tokens.exs
new file mode 100644
index 000000000..fe67db8cc
--- /dev/null
+++ b/priv/repo/migrations/20191026191401_set_not_null_for_oauth_tokens.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForOauthTokens do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE oauth_tokens
+    ALTER COLUMN app_id SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE oauth_tokens
+    ALTER COLUMN app_id DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191442_set_not_null_for_objects.exs b/priv/repo/migrations/20191026191442_set_not_null_for_objects.exs
new file mode 100644
index 000000000..59e89d6da
--- /dev/null
+++ b/priv/repo/migrations/20191026191442_set_not_null_for_objects.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForObjects do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE objects
+    ALTER COLUMN data SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE objects
+    ALTER COLUMN data DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191524_set_not_null_for_password_reset_tokens.exs b/priv/repo/migrations/20191026191524_set_not_null_for_password_reset_tokens.exs
new file mode 100644
index 000000000..51efadbd3
--- /dev/null
+++ b/priv/repo/migrations/20191026191524_set_not_null_for_password_reset_tokens.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForPasswordResetTokens do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE password_reset_tokens
+    ALTER COLUMN token SET NOT NULL,
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN used SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE password_reset_tokens
+    ALTER COLUMN token DROP NOT NULL,
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN used DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191603_set_not_null_for_push_subscriptions.exs b/priv/repo/migrations/20191026191603_set_not_null_for_push_subscriptions.exs
new file mode 100644
index 000000000..0f3a73067
--- /dev/null
+++ b/priv/repo/migrations/20191026191603_set_not_null_for_push_subscriptions.exs
@@ -0,0 +1,25 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForPushSubscriptions do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE push_subscriptions
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN token_id SET NOT NULL,
+    ALTER COLUMN endpoint SET NOT NULL,
+    ALTER COLUMN key_p256dh SET NOT NULL,
+    ALTER COLUMN key_auth SET NOT NULL,
+    ALTER COLUMN data SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE push_subscriptions
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN token_id DROP NOT NULL,
+    ALTER COLUMN endpoint DROP NOT NULL,
+    ALTER COLUMN key_p256dh DROP NOT NULL,
+    ALTER COLUMN key_auth DROP NOT NULL,
+    ALTER COLUMN data DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191635_set_not_null_for_registrations.exs b/priv/repo/migrations/20191026191635_set_not_null_for_registrations.exs
new file mode 100644
index 000000000..ddfbf4c5e
--- /dev/null
+++ b/priv/repo/migrations/20191026191635_set_not_null_for_registrations.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForRegistrations do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE registrations
+    ALTER COLUMN provider SET NOT NULL,
+    ALTER COLUMN uid SET NOT NULL,
+    ALTER COLUMN info SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE registrations
+    ALTER COLUMN provider DROP NOT NULL,
+    ALTER COLUMN uid DROP NOT NULL,
+    ALTER COLUMN info DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191711_set_not_null_for_scheduled_activities.exs b/priv/repo/migrations/20191026191711_set_not_null_for_scheduled_activities.exs
new file mode 100644
index 000000000..f1830c8c3
--- /dev/null
+++ b/priv/repo/migrations/20191026191711_set_not_null_for_scheduled_activities.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForScheduledActivities do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE scheduled_activities
+    ALTER COLUMN user_id SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE scheduled_activities
+    ALTER COLUMN user_id DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191753_set_not_null_for_thread_mutes.exs b/priv/repo/migrations/20191026191753_set_not_null_for_thread_mutes.exs
new file mode 100644
index 000000000..daa7ce314
--- /dev/null
+++ b/priv/repo/migrations/20191026191753_set_not_null_for_thread_mutes.exs
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForThreadMutes do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE thread_mutes
+    ALTER COLUMN user_id SET NOT NULL,
+    ALTER COLUMN context SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE thread_mutes
+    ALTER COLUMN user_id DROP NOT NULL,
+    ALTER COLUMN context DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191826_set_not_null_for_user_invite_tokens.exs b/priv/repo/migrations/20191026191826_set_not_null_for_user_invite_tokens.exs
new file mode 100644
index 000000000..836544f7f
--- /dev/null
+++ b/priv/repo/migrations/20191026191826_set_not_null_for_user_invite_tokens.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForUserInviteTokens do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    execute("ALTER TABLE user_invite_tokens
+    ALTER COLUMN used SET NOT NULL,
+    ALTER COLUMN uses SET NOT NULL,
+    ALTER COLUMN invite_type SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE user_invite_tokens
+    ALTER COLUMN used DROP NOT NULL,
+    ALTER COLUMN uses DROP NOT NULL,
+    ALTER COLUMN invite_type DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191026191910_set_not_null_for_users.exs b/priv/repo/migrations/20191026191910_set_not_null_for_users.exs
new file mode 100644
index 000000000..9d8d0ccf8
--- /dev/null
+++ b/priv/repo/migrations/20191026191910_set_not_null_for_users.exs
@@ -0,0 +1,44 @@
+defmodule Pleroma.Repo.Migrations.SetNotNullForUsers do
+  use Ecto.Migration
+
+  # modify/3 function will require index recreation, so using execute/1 instead
+
+  def up do
+    # irreversible
+    execute("UPDATE users SET follower_count = 0 WHERE follower_count IS NULL")
+
+    execute("ALTER TABLE users
+    ALTER COLUMN local SET NOT NULL,
+    ALTER COLUMN source_data SET NOT NULL,
+    ALTER COLUMN note_count SET NOT NULL,
+    ALTER COLUMN follower_count SET NOT NULL,
+    ALTER COLUMN blocks SET NOT NULL,
+    ALTER COLUMN domain_blocks SET NOT NULL,
+    ALTER COLUMN mutes SET NOT NULL,
+    ALTER COLUMN muted_reblogs SET NOT NULL,
+    ALTER COLUMN muted_notifications SET NOT NULL,
+    ALTER COLUMN subscribers SET NOT NULL,
+    ALTER COLUMN pinned_activities SET NOT NULL,
+    ALTER COLUMN emoji SET NOT NULL,
+    ALTER COLUMN fields SET NOT NULL,
+    ALTER COLUMN raw_fields SET NOT NULL")
+  end
+
+  def down do
+    execute("ALTER TABLE users
+    ALTER COLUMN local DROP NOT NULL,
+    ALTER COLUMN source_data DROP NOT NULL,
+    ALTER COLUMN note_count DROP NOT NULL,
+    ALTER COLUMN follower_count DROP NOT NULL,
+    ALTER COLUMN blocks DROP NOT NULL,
+    ALTER COLUMN domain_blocks DROP NOT NULL,
+    ALTER COLUMN mutes DROP NOT NULL,
+    ALTER COLUMN muted_reblogs DROP NOT NULL,
+    ALTER COLUMN muted_notifications DROP NOT NULL,
+    ALTER COLUMN subscribers DROP NOT NULL,
+    ALTER COLUMN pinned_activities DROP NOT NULL,
+    ALTER COLUMN emoji DROP NOT NULL,
+    ALTER COLUMN fields DROP NOT NULL,
+    ALTER COLUMN raw_fields DROP NOT NULL")
+  end
+end
diff --git a/priv/repo/migrations/20191029101340_migrate_missing_follow_requests.exs b/priv/repo/migrations/20191029101340_migrate_missing_follow_requests.exs
new file mode 100644
index 000000000..90b18efc8
--- /dev/null
+++ b/priv/repo/migrations/20191029101340_migrate_missing_follow_requests.exs
@@ -0,0 +1,35 @@
+defmodule Pleroma.Repo.Migrations.MigrateMissingFollowingRelationships do
+  use Ecto.Migration
+
+  def change do
+    execute(import_pending_follows_from_activities(), "")
+  end
+
+  defp import_pending_follows_from_activities do
+    """
+    INSERT INTO
+        following_relationships (
+            follower_id,
+            following_id,
+            state,
+            inserted_at,
+            updated_at
+        )
+    SELECT
+        followers.id,
+        following.id,
+        activities.data ->> 'state',
+        (activities.data ->> 'published') :: timestamp,
+        now()
+    FROM
+        activities
+        JOIN users AS followers ON (activities.actor = followers.ap_id)
+        JOIN users AS following ON (activities.data ->> 'object' = following.ap_id)
+    WHERE
+        activities.data ->> 'type' = 'Follow'
+        AND activities.data ->> 'state' = 'pending'
+    ORDER BY activities.updated_at DESC
+    ON CONFLICT DO NOTHING
+    """
+  end
+end
diff --git a/rel/files/bin/pleroma_ctl b/rel/files/bin/pleroma_ctl
index 9fc5b0bad..87c486514 100755
--- a/rel/files/bin/pleroma_ctl
+++ b/rel/files/bin/pleroma_ctl
@@ -140,11 +140,15 @@ else
 	FULL_ARGS="$*"
 
 	ACTION="$1"
-	shift
-        echo "$1" | grep "^-" >/dev/null
+	if [ $# -gt 0 ]; then
+		shift
+	fi
+	echo "$1" | grep "^-" >/dev/null
 	if [ $? -eq 1 ]; then
 		SUBACTION="$1"
-		shift
+		if [ $# -gt 0 ]; then
+			shift
+		fi
 	fi
 
 	if [ "$ACTION" = "update" ]; then
diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs
index 91867bf70..863270022 100644
--- a/test/conversation/participation_test.exs
+++ b/test/conversation/participation_test.exs
@@ -140,7 +140,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
     participation2 = insert(:participation, %{read: false, user: user})
     participation3 = insert(:participation, %{read: false, user: other_user})
 
-    {:ok, [%{read: true}, %{read: true}]} = Participation.mark_all_as_read(user)
+    {:ok, _, [%{read: true}, %{read: true}]} = Participation.mark_all_as_read(user)
 
     assert Participation.get(participation1.id).read == true
     assert Participation.get(participation2.id).read == true
@@ -216,4 +216,134 @@ defmodule Pleroma.Conversation.ParticipationTest do
     assert user in participation.recipients
     assert other_user in participation.recipients
   end
+
+  describe "blocking" do
+    test "when the user blocks a recipient, the existing conversations with them are marked as read" do
+      blocker = insert(:user)
+      blocked = insert(:user)
+      third_user = insert(:user)
+
+      {:ok, _direct1} =
+        CommonAPI.post(third_user, %{
+          "status" => "Hi @#{blocker.nickname}",
+          "visibility" => "direct"
+        })
+
+      {:ok, _direct2} =
+        CommonAPI.post(third_user, %{
+          "status" => "Hi @#{blocker.nickname}, @#{blocked.nickname}",
+          "visibility" => "direct"
+        })
+
+      {:ok, _direct3} =
+        CommonAPI.post(blocked, %{
+          "status" => "Hi @#{blocker.nickname}",
+          "visibility" => "direct"
+        })
+
+      {:ok, _direct4} =
+        CommonAPI.post(blocked, %{
+          "status" => "Hi @#{blocker.nickname}, @#{third_user.nickname}",
+          "visibility" => "direct"
+        })
+
+      assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] =
+               Participation.for_user(blocker)
+
+      assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4
+
+      {:ok, blocker} = User.block(blocker, blocked)
+
+      # The conversations with the blocked user are marked as read
+      assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
+               Participation.for_user(blocker)
+
+      assert User.get_cached_by_id(blocker.id).unread_conversation_count == 1
+
+      # The conversation is not marked as read for the blocked user
+      assert [_, _, %{read: false}] = Participation.for_user(blocked)
+      assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+
+      # The conversation is not marked as read for the third user
+      assert [%{read: false}, _, _] = Participation.for_user(third_user)
+      assert User.get_cached_by_id(third_user.id).unread_conversation_count == 1
+    end
+
+    test "the new conversation with the blocked user is not marked as unread " do
+      blocker = insert(:user)
+      blocked = insert(:user)
+      third_user = insert(:user)
+
+      {:ok, blocker} = User.block(blocker, blocked)
+
+      # When the blocked user is the author
+      {:ok, _direct1} =
+        CommonAPI.post(blocked, %{
+          "status" => "Hi @#{blocker.nickname}",
+          "visibility" => "direct"
+        })
+
+      assert [%{read: true}] = Participation.for_user(blocker)
+      assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+
+      # When the blocked user is a recipient
+      {:ok, _direct2} =
+        CommonAPI.post(third_user, %{
+          "status" => "Hi @#{blocker.nickname}, @#{blocked.nickname}",
+          "visibility" => "direct"
+        })
+
+      assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
+      assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+
+      assert [%{read: false}, _] = Participation.for_user(blocked)
+      assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+    end
+
+    test "the conversation with the blocked user is not marked as unread on a reply" do
+      blocker = insert(:user)
+      blocked = insert(:user)
+      third_user = insert(:user)
+
+      {:ok, _direct1} =
+        CommonAPI.post(blocker, %{
+          "status" => "Hi @#{third_user.nickname}, @#{blocked.nickname}",
+          "visibility" => "direct"
+        })
+
+      {:ok, blocker} = User.block(blocker, blocked)
+      assert [%{read: true}] = Participation.for_user(blocker)
+      assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+
+      assert [blocked_participation] = Participation.for_user(blocked)
+
+      # When it's a reply from the blocked user
+      {:ok, _direct2} =
+        CommonAPI.post(blocked, %{
+          "status" => "reply",
+          "visibility" => "direct",
+          "in_reply_to_conversation_id" => blocked_participation.id
+        })
+
+      assert [%{read: true}] = Participation.for_user(blocker)
+      assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+
+      assert [third_user_participation] = Participation.for_user(third_user)
+
+      # When it's a reply from the third user
+      {:ok, _direct3} =
+        CommonAPI.post(third_user, %{
+          "status" => "reply",
+          "visibility" => "direct",
+          "in_reply_to_conversation_id" => third_user_participation.id
+        })
+
+      assert [%{read: true}] = Participation.for_user(blocker)
+      assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0
+
+      # Marked as unread for the blocked user
+      assert [%{read: false}] = Participation.for_user(blocked)
+      assert User.get_cached_by_id(blocked.id).unread_conversation_count == 1
+    end
+  end
 end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 41e2b8004..e3f797f64 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -39,8 +39,7 @@ defmodule Pleroma.Factory do
       user
       | ap_id: User.ap_id(user),
         follower_address: User.ap_followers(user),
-        following_address: User.ap_following(user),
-        following: [User.ap_id(user)]
+        following_address: User.ap_following(user)
     }
   end
 
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
index b66324e5e..0c7883f33 100644
--- a/test/tasks/database_test.exs
+++ b/test/tasks/database_test.exs
@@ -72,24 +72,25 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
   describe "running update_users_following_followers_counts" do
     test "following and followers count are updated" do
       [user, user2] = insert_pair(:user)
-      {:ok, %User{following: following} = user} = User.follow(user, user2)
+      {:ok, %User{} = user} = User.follow(user, user2)
+
+      following = User.following(user)
 
       assert length(following) == 2
       assert user.follower_count == 0
 
       {:ok, user} =
         user
-        |> Ecto.Changeset.change(%{following: following ++ following, follower_count: 3})
+        |> Ecto.Changeset.change(%{follower_count: 3})
         |> Repo.update()
 
-      assert length(user.following) == 4
       assert user.follower_count == 3
 
       assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
 
       user = User.get_by_id(user.id)
 
-      assert length(user.following) == 2
+      assert length(User.following(user)) == 2
       assert user.follower_count == 0
     end
   end
diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs
index c866608ab..04a1e45d7 100644
--- a/test/tasks/relay_test.exs
+++ b/test/tasks/relay_test.exs
@@ -51,7 +51,7 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
       target_user = User.get_cached_by_ap_id(target_instance)
       follow_activity = Utils.fetch_latest_follow(local_user, target_user)
       User.follow(local_user, target_user)
-      assert "#{target_instance}/followers" in refresh_record(local_user).following
+      assert "#{target_instance}/followers" in User.following(local_user)
       Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance])
 
       cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
@@ -68,7 +68,7 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
       assert undo_activity.data["type"] == "Undo"
       assert undo_activity.data["actor"] == local_user.ap_id
       assert undo_activity.data["object"] == cancelled_activity.data
-      refute "#{target_instance}/followers" in refresh_record(local_user).following
+      refute "#{target_instance}/followers" in User.following(local_user)
     end
   end
 
@@ -78,20 +78,18 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
 
       refute_receive {:mix_shell, :info, _}
 
-      Pleroma.Web.ActivityPub.Relay.get_actor()
-      |> Ecto.Changeset.change(
-        following: [
-          "http://test-app.com/user/test1",
-          "http://test-app.com/user/test1",
-          "http://test-app-42.com/user/test1"
-        ]
-      )
-      |> Pleroma.User.update_and_set_cache()
+      relay_user = Relay.get_actor()
+
+      ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
+      |> Enum.each(fn ap_id ->
+        {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
+        User.follow(relay_user, user)
+      end)
 
       :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
 
-      assert_receive {:mix_shell, :info, ["test-app.com"]}
-      assert_receive {:mix_shell, :info, ["test-app-42.com"]}
+      assert_receive {:mix_shell, :info, ["mstdn.io"]}
+      assert_receive {:mix_shell, :info, ["mastodon.example.org"]}
     end
   end
 end
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index f024f92ae..bfd0ccbc5 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -139,7 +139,8 @@ defmodule Mix.Tasks.Pleroma.UserTest do
   describe "running unsubscribe" do
     test "user is unsubscribed" do
       followed = insert(:user)
-      user = insert(:user, %{following: [User.ap_followers(followed)]})
+      user = insert(:user)
+      User.follow(user, followed, "accept")
 
       Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
 
@@ -154,7 +155,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message =~ "Successfully unsubscribed"
 
       user = User.get_cached_by_nickname(user.nickname)
-      assert Enum.empty?(user.following)
+      assert Enum.empty?(User.get_friends(user))
       assert user.deactivated
     end
 
diff --git a/test/user_test.exs b/test/user_test.exs
index 92de31c74..188295a86 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -88,10 +88,9 @@ defmodule Pleroma.UserTest do
     CommonAPI.follow(pending_follower, locked)
     CommonAPI.follow(pending_follower, locked)
     CommonAPI.follow(accepted_follower, locked)
-    User.follow(accepted_follower, locked)
+    Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept")
 
-    assert [activity] = User.get_follow_requests(locked)
-    assert activity
+    assert [^pending_follower] = User.get_follow_requests(locked)
   end
 
   test "clears follow requests when requester is blocked" do
@@ -136,10 +135,10 @@ defmodule Pleroma.UserTest do
     followed_two = insert(:user)
 
     {:ok, user} = User.follow_all(user, [followed_zero, followed_one])
-    assert length(user.following) == 3
+    assert length(User.following(user)) == 3
 
     {:ok, user} = User.follow_all(user, [followed_one, followed_two])
-    assert length(user.following) == 4
+    assert length(User.following(user)) == 4
   end
 
   test "follow takes a user and another user" do
@@ -153,7 +152,7 @@ defmodule Pleroma.UserTest do
     followed = User.get_cached_by_ap_id(followed.ap_id)
     assert followed.follower_count == 1
 
-    assert User.ap_followers(followed) in user.following
+    assert User.ap_followers(followed) in User.following(user)
   end
 
   test "can't follow a deactivated users" do
@@ -218,26 +217,29 @@ defmodule Pleroma.UserTest do
           nickname: "fuser2",
           ap_id: "http://localhost:4001/users/fuser2",
           follower_address: "http://localhost:4001/users/fuser2/followers",
-          following_address: "http://localhost:4001/users/fuser2/following",
-          following: [User.ap_followers(followed)]
+          following_address: "http://localhost:4001/users/fuser2/following"
         })
 
+      {:ok, user} = User.follow(user, followed, "accept")
+
       {:ok, user, _activity} = User.unfollow(user, followed)
 
       user = User.get_cached_by_id(user.id)
 
-      assert user.following == []
+      assert User.following(user) == []
     end
 
     test "unfollow takes a user and another user" do
       followed = insert(:user)
-      user = insert(:user, %{following: [User.ap_followers(followed)]})
+      user = insert(:user)
+
+      {:ok, user} = User.follow(user, followed, "accept")
+
+      assert User.following(user) == [user.follower_address, followed.follower_address]
 
       {:ok, user, _activity} = User.unfollow(user, followed)
 
-      user = User.get_cached_by_id(user.id)
-
-      assert user.following == []
+      assert User.following(user) == [user.follower_address]
     end
 
     test "unfollow doesn't unfollow yourself" do
@@ -245,14 +247,14 @@ defmodule Pleroma.UserTest do
 
       {:error, _} = User.unfollow(user, user)
 
-      user = User.get_cached_by_id(user.id)
-      assert user.following == [user.ap_id]
+      assert User.following(user) == [user.follower_address]
     end
   end
 
   test "test if a user is following another user" do
     followed = insert(:user)
-    user = insert(:user, %{following: [User.ap_followers(followed)]})
+    user = insert(:user)
+    User.follow(user, followed, "accept")
 
     assert User.following?(user, followed)
     refute User.following?(followed, user)
@@ -335,7 +337,7 @@ defmodule Pleroma.UserTest do
       refute changeset.valid?
     end
 
-    test "it sets the password_hash, ap_id and following fields" do
+    test "it sets the password_hash and ap_id" do
       changeset = User.register_changeset(%User{}, @full_user_data)
 
       assert changeset.valid?
@@ -343,10 +345,6 @@ defmodule Pleroma.UserTest do
       assert is_binary(changeset.changes[:password_hash])
       assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
 
-      assert changeset.changes[:following] == [
-               User.ap_followers(%User{nickname: @full_user_data.nickname})
-             ]
-
       assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
     end
 
@@ -650,37 +648,6 @@ defmodule Pleroma.UserTest do
     end
   end
 
-  describe "remove duplicates from following list" do
-    test "it removes duplicates" do
-      user = insert(:user)
-      follower = insert(:user)
-
-      {:ok, %User{following: following} = follower} = User.follow(follower, user)
-      assert length(following) == 2
-
-      {:ok, follower} =
-        follower
-        |> User.update_changeset(%{following: following ++ following})
-        |> Repo.update()
-
-      assert length(follower.following) == 4
-
-      {:ok, follower} = User.remove_duplicated_following(follower)
-      assert length(follower.following) == 2
-    end
-
-    test "it does nothing when following is uniq" do
-      user = insert(:user)
-      follower = insert(:user)
-
-      {:ok, follower} = User.follow(follower, user)
-      assert length(follower.following) == 2
-
-      {:ok, follower} = User.remove_duplicated_following(follower)
-      assert length(follower.following) == 2
-    end
-  end
-
   describe "follow_import" do
     test "it imports user followings from list" do
       [user1, user2, user3] = insert_list(3, :user)
@@ -989,7 +956,9 @@ defmodule Pleroma.UserTest do
       assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
 
       assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] ==
-               ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
+               ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{
+                 "user" => user2
+               })
 
       {:ok, _user} = User.deactivate(user)
 
@@ -997,7 +966,9 @@ defmodule Pleroma.UserTest do
       assert [] == Pleroma.Notification.for_user(user2)
 
       assert [] ==
-               ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
+               ActivityPub.fetch_activities([user2.ap_id | User.following(user2)], %{
+                 "user" => user2
+               })
     end
   end
 
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 4bb6333c0..0e5eb0c50 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -693,7 +693,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
     {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
 
-    [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following])
+    [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
 
     assert announce_activity.id == announce.id
   end
@@ -1219,7 +1219,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
         })
 
       activities =
-        ActivityPub.fetch_activities([user1.ap_id | user1.following])
+        ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
         |> Enum.map(fn a -> a.id end)
 
       private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
@@ -1229,7 +1229,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert length(activities) == 3
 
       activities =
-        ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
+        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
diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs
index ac2007b2c..98dc78f46 100644
--- a/test/web/activity_pub/relay_test.exs
+++ b/test/web/activity_pub/relay_test.exs
@@ -56,14 +56,14 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
       service_actor = Relay.get_actor()
       ActivityPub.follow(service_actor, user)
       Pleroma.User.follow(service_actor, user)
-      assert "#{user.ap_id}/followers" in refresh_record(service_actor).following
+      assert "#{user.ap_id}/followers" in User.following(service_actor)
       assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
       assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
       assert user.ap_id in activity.recipients
       assert activity.data["type"] == "Undo"
       assert activity.data["actor"] == service_actor.ap_id
       assert activity.data["to"] == [user.ap_id]
-      refute "#{user.ap_id}/followers" in refresh_record(service_actor).following
+      refute "#{user.ap_id}/followers" in User.following(service_actor)
     end
   end
 
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index ae56f8494..d920b969a 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -720,7 +720,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert capture_log(fn ->
                :error = Transmogrifier.handle_incoming(data)
              end) =~
-               "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}"
+               "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}"
 
       assert Activity.get_by_id(activity.id)
     end
@@ -804,6 +804,25 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
     end
 
+    test "it works for incoming follows to locked account" do
+      pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
+      user = insert(:user, locked: true)
+
+      data =
+        File.read!("test/fixtures/mastodon-follow-activity.json")
+        |> Poison.decode!()
+        |> Map.put("object", user.ap_id)
+
+      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+
+      assert data["type"] == "Follow"
+      assert data["object"] == user.ap_id
+      assert data["state"] == "pending"
+      assert data["actor"] == "http://mastodon.example.org/users/admin"
+
+      assert [^pending_follower] = User.get_follow_requests(user)
+    end
+
     test "it works for incoming blocks" do
       user = insert(:user)
 
@@ -1334,7 +1353,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
           follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
         })
 
-      user_two = insert(:user, %{following: [user.follower_address]})
+      user_two = insert(:user)
+      Pleroma.FollowingRelationship.follow(user_two, user, "accept")
 
       {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
       {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
@@ -1381,8 +1401,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       refute user.follower_address in unrelated_activity.recipients
 
       user_two = User.get_cached_by_id(user_two.id)
-      assert user.follower_address in user_two.following
-      refute "..." in user_two.following
+      assert User.following?(user_two, user)
+      refute "..." in User.following(user_two)
     end
   end
 
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs
index b62a89e68..4c2e0d207 100644
--- a/test/web/activity_pub/visibilty_test.exs
+++ b/test/web/activity_pub/visibilty_test.exs
@@ -212,7 +212,8 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
 
     test "returns true if user following to author" do
       author = insert(:user)
-      user = insert(:user, following: [author.ap_id])
+      user = insert(:user)
+      Pleroma.User.follow(user, author)
 
       activity =
         insert(:note_activity,
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 58435d23c..22c989892 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -2572,22 +2572,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "GET /relay", %{admin: admin} do
-      Pleroma.Web.ActivityPub.Relay.get_actor()
-      |> Ecto.Changeset.change(
-        following: [
-          "http://test-app.com/user/test1",
-          "http://test-app.com/user/test1",
-          "http://test-app-42.com/user/test1"
-        ]
-      )
-      |> Pleroma.User.update_and_set_cache()
+      relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
+
+      ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
+      |> Enum.each(fn ap_id ->
+        {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
+        User.follow(relay_user, user)
+      end)
 
       conn =
         build_conn()
         |> assign(:user, admin)
         |> get("/api/pleroma/admin/relay")
 
-      assert json_response(conn, 200)["relays"] -- ["test-app.com", "test-app-42.com"] == []
+      assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
     end
 
     test "DELETE /relay", %{admin: admin} do
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 00c83fd7b..8fc2d9300 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -471,7 +471,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn =
         build_conn()
-        |> assign(:user, follower)
+        |> assign(:user, User.get_cached_by_id(follower.id))
         |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
 
       assert %{"showing_reblogs" => true} = json_response(conn, 200)
diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
index 913f8c038..288cd9029 100644
--- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs
+++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
@@ -16,9 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
-
-      user = User.get_cached_by_id(user.id)
-      other_user = User.get_cached_by_id(other_user.id)
+      {:ok, other_user} = User.follow(other_user, user, "pending")
 
       assert User.following?(other_user, user) == false
 
@@ -36,6 +34,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
+      {:ok, other_user} = User.follow(other_user, user, "pending")
 
       user = User.get_cached_by_id(user.id)
       other_user = User.get_cached_by_id(other_user.id)
diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs
index 400f3287d..cb1015171 100644
--- a/test/web/streamer/streamer_test.exs
+++ b/test/web/streamer/streamer_test.exs
@@ -169,7 +169,8 @@ defmodule Pleroma.Web.StreamerTest do
     test "it doesn't send to user if recipients invalid and thread containment is enabled" do
       Pleroma.Config.put([:instance, :skip_thread_containment], false)
       author = insert(:user)
-      user = insert(:user, following: [author.ap_id])
+      user = insert(:user)
+      User.follow(user, author, "accept")
 
       activity =
         insert(:note_activity,
@@ -191,7 +192,8 @@ defmodule Pleroma.Web.StreamerTest do
     test "it sends message if recipients invalid and thread containment is disabled" do
       Pleroma.Config.put([:instance, :skip_thread_containment], true)
       author = insert(:user)
-      user = insert(:user, following: [author.ap_id])
+      user = insert(:user)
+      User.follow(user, author, "accept")
 
       activity =
         insert(:note_activity,
@@ -213,7 +215,8 @@ defmodule Pleroma.Web.StreamerTest do
     test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do
       Pleroma.Config.put([:instance, :skip_thread_containment], false)
       author = insert(:user)
-      user = insert(:user, following: [author.ap_id], skip_thread_containment: true)
+      user = insert(:user, skip_thread_containment: true)
+      User.follow(user, author, "accept")
 
       activity =
         insert(:note_activity,
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index 246da4da4..f0211f59c 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -366,7 +366,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
         |> response(200)
 
       assert response =~ "Account followed!"
-      assert user2.follower_address in refresh_record(user).following
+      assert user2.follower_address in User.following(user)
     end
 
     test "returns error when user is deactivated", %{conn: conn} do
@@ -438,7 +438,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
         |> response(200)
 
       assert response =~ "Account followed!"
-      assert user2.follower_address in refresh_record(user).following
+      assert user2.follower_address in User.following(user)
     end
 
     test "returns error when followee not found", %{conn: conn} do