diff options
66 files changed, 1283 insertions, 377 deletions
@@ -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) - -    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 -      ) +    followeds = +      Enum.reject(followeds, fn followed -> +        blocks?(follower, followed) || blocks?(followed, follower) +      end) -    {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, _activity} = User.unfollow(user, followed) +      {:ok, user} = User.follow(user, followed, "accept") -      user = User.get_cached_by_id(user.id) +      assert User.following(user) == [user.follower_address, followed.follower_address] + +      {:ok, user, _activity} = User.unfollow(user, followed) -      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  | 
