Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
This commit is contained in:
		
						commit
						55ce1daeaf
					
				
					 63 changed files with 1210 additions and 372 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,10 +39,12 @@ def query_timelines(user) 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 @@ def query_timelines(user) do
 | 
			
		|||
 | 
			
		||||
    home_activities =
 | 
			
		||||
      Pleroma.Web.ActivityPub.ActivityPub.fetch_activities(
 | 
			
		||||
        [user.ap_id | user.following],
 | 
			
		||||
        following,
 | 
			
		||||
        home_timeline_params
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,15 +45,13 @@ defp generate_user_data(i) 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,9 +52,9 @@ def run(["bump_all_conversations"]) 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -163,7 +163,7 @@ def run(["unsubscribe", nickname]) 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,7 +97,7 @@ def handle_command(state, "home") 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 ->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,13 @@ def create_or_bump_for(activity, opts \\ []) 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,11 +32,20 @@ def creation_cng(struct, params) 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 @@ def mark_as_read(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 @@ def mark_all_as_read(user) 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										110
									
								
								lib/pleroma/following_relationship.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								lib/pleroma/following_relationship.ex
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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 @@ def restrict_deactivated(query) 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 @@ def update_changeset(struct, params \\ %{}) do
 | 
			
		|||
        :bio,
 | 
			
		||||
        :name,
 | 
			
		||||
        :avatar,
 | 
			
		||||
        :following,
 | 
			
		||||
        :locked,
 | 
			
		||||
        :no_rich_text,
 | 
			
		||||
        :default_scope,
 | 
			
		||||
| 
						 | 
				
			
			@ -502,7 +447,6 @@ defp put_following_and_follower_address(changeset) 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 @@ def needs_update?(%User{local: false} = 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 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do
 | 
			
		|||
  @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
 | 
			
		||||
  @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
 | 
			
		||||
  def follow_all(follower, followeds) do
 | 
			
		||||
    followed_addresses =
 | 
			
		||||
      followeds
 | 
			
		||||
      |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
 | 
			
		||||
      |> Enum.map(fn %{follower_address: fa} -> fa end)
 | 
			
		||||
    followeds =
 | 
			
		||||
      Enum.reject(followeds, fn followed ->
 | 
			
		||||
        blocks?(follower, followed) || blocks?(followed, follower)
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
    q =
 | 
			
		||||
      from(u in User,
 | 
			
		||||
        where: u.id == ^follower.id,
 | 
			
		||||
        update: [
 | 
			
		||||
          set: [
 | 
			
		||||
            following:
 | 
			
		||||
              fragment(
 | 
			
		||||
                "array(select distinct unnest (array_cat(?, ?)))",
 | 
			
		||||
                u.following,
 | 
			
		||||
                ^followed_addresses
 | 
			
		||||
              )
 | 
			
		||||
          ]
 | 
			
		||||
        ],
 | 
			
		||||
        select: u
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
    {1, [follower]} = Repo.update_all(q, [])
 | 
			
		||||
    Enum.each(followeds, &follow(follower, &1, "accept"))
 | 
			
		||||
 | 
			
		||||
    Enum.each(followeds, &update_follower_count/1)
 | 
			
		||||
 | 
			
		||||
    set_cache(follower)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def follow(%User{} = follower, %User{} = followed) do
 | 
			
		||||
  defdelegate following(user), to: FollowingRelationship
 | 
			
		||||
 | 
			
		||||
  def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
 | 
			
		||||
    deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
 | 
			
		||||
    ap_followers = followed.follower_address
 | 
			
		||||
 | 
			
		||||
    cond do
 | 
			
		||||
      followed.deactivated ->
 | 
			
		||||
| 
						 | 
				
			
			@ -615,14 +544,7 @@ def follow(%User{} = follower, %User{} = followed) 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 @@ def follow(%User{} = follower, %User{} = followed) 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 @@ def unfollow(%User{} = follower, %User{} = followed) 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 @@ def get_friends_ids(user, page \\ nil) 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 @@ def set_unread_conversation_count(%User{local: true} = 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 @@ def increment_unread_conversation_count(conversation, %User{local: true} = user)
 | 
			
		|||
    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 @@ def block(blocker, %User{ap_id: ap_id} = blocked) 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ defp compose_query({:deactivated, true}, 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -518,7 +518,9 @@ defp fetch_activities_for_context_query(context, opts) 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 @@ defp user_activities_recipients(%{"godmode" => true}) 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -319,12 +319,12 @@ def read_inbox(
 | 
			
		|||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,9 +57,10 @@ def publish(_), do: {:error, "Not implemented"}
 | 
			
		|||
 | 
			
		||||
  @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()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ def handle_incoming(
 | 
			
		|||
           {_, 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 @@ def handle_incoming(
 | 
			
		|||
      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 @@ def handle_incoming(
 | 
			
		|||
 | 
			
		||||
        {:follow, {:error, _}} ->
 | 
			
		||||
          {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
 | 
			
		||||
          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
 | 
			
		||||
 | 
			
		||||
          ActivityPub.reject(%{
 | 
			
		||||
            to: [follower.ap_id],
 | 
			
		||||
| 
						 | 
				
			
			@ -522,7 +526,7 @@ def handle_incoming(
 | 
			
		|||
         {: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 +549,7 @@ def handle_incoming(
 | 
			
		|||
         {: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 +559,6 @@ def handle_incoming(
 | 
			
		|||
             local: false,
 | 
			
		||||
             activity_id: id
 | 
			
		||||
           }) do
 | 
			
		||||
      User.unfollow(follower, followed)
 | 
			
		||||
 | 
			
		||||
      {:ok, activity}
 | 
			
		||||
    else
 | 
			
		||||
      _e -> :error
 | 
			
		||||
| 
						 | 
				
			
			@ -1061,43 +1064,22 @@ def perform(:user_upgrade, user) 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ def visible_for_user?(activity, nil) 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ def accept_follow_request(follower, followed) 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 @@ def accept_follow_request(follower, followed) 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],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 @@ def home(%{assigns: %{user: user}} = conn, params) 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 @@ def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) 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()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -126,7 +126,7 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ def update_conversation(
 | 
			
		|||
  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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										16
									
								
								priv/repo/migrations/20191008132427_drop_users_following.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								priv/repo/migrations/20191008132427_drop_users_following.exs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +140,7 @@ test "it marks all the user's participations as read" 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 @@ test "it sets recipients, always keeping the owner of the participation even whe
 | 
			
		|||
    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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,8 +39,7 @@ def user_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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -72,24 +72,25 @@ test "it prunes old objects from the database" 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ test "relay is unfollowed" 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 @@ test "relay is unfollowed" 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 @@ test "Prints relay subscription list" 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -139,7 +139,8 @@ test "no user to toggle" 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 @@ test "user is unsubscribed" 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,10 +88,9 @@ test "doesn't return already accepted or duplicate follow requests" 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 @@ test "follow_all follows mutliple users without duplicating" 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 @@ test "follow takes a user and another user" 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 @@ test "unfollow with syncronizes external user" do
 | 
			
		|||
          nickname: "fuser2",
 | 
			
		||||
          ap_id: "http://localhost:4001/users/fuser2",
 | 
			
		||||
          follower_address: "http://localhost:4001/users/fuser2/followers",
 | 
			
		||||
          following_address: "http://localhost:4001/users/fuser2/following",
 | 
			
		||||
          following: [User.ap_followers(followed)]
 | 
			
		||||
          following_address: "http://localhost:4001/users/fuser2/following"
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
      {:ok, user} = User.follow(user, followed, "accept")
 | 
			
		||||
 | 
			
		||||
      {:ok, user, _activity} = User.unfollow(user, followed)
 | 
			
		||||
 | 
			
		||||
      user = User.get_cached_by_id(user.id)
 | 
			
		||||
 | 
			
		||||
      assert user.following == []
 | 
			
		||||
      assert User.following(user) == []
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "unfollow takes a user and another user" do
 | 
			
		||||
      followed = insert(:user)
 | 
			
		||||
      user = insert(:user, %{following: [User.ap_followers(followed)]})
 | 
			
		||||
      user = insert(:user)
 | 
			
		||||
 | 
			
		||||
      {:ok, user} = User.follow(user, followed, "accept")
 | 
			
		||||
 | 
			
		||||
      assert User.following(user) == [user.follower_address, followed.follower_address]
 | 
			
		||||
 | 
			
		||||
      {:ok, user, _activity} = User.unfollow(user, followed)
 | 
			
		||||
 | 
			
		||||
      user = User.get_cached_by_id(user.id)
 | 
			
		||||
 | 
			
		||||
      assert user.following == []
 | 
			
		||||
      assert User.following(user) == [user.follower_address]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "unfollow doesn't unfollow yourself" do
 | 
			
		||||
| 
						 | 
				
			
			@ -245,14 +247,14 @@ test "unfollow doesn't unfollow yourself" 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 @@ test "it restricts certain nicknames" 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 @@ test "it sets the password_hash, ap_id and following fields" 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 @@ test "it sets the follower_count property" 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 @@ test "hide a user's statuses from timelines and notifications" 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 @@ test "hide a user's statuses from timelines and notifications" 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -693,7 +693,7 @@ test "does include announces on request" 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 @@ test "it filters broken threads" 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 @@ test "it filters broken threads" 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,14 +56,14 @@ test "returns activity" 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1334,7 +1334,8 @@ test "it upgrades a user to activitypub" 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 +1382,8 @@ test "it upgrades a user to activitypub" 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -212,7 +212,8 @@ test "returns false when invalid recipients", %{user: user} 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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2572,22 +2572,20 @@ test "POST /relay", %{admin: admin} 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -471,7 +471,7 @@ test "following without reblogs" 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,9 +16,7 @@ test "/api/v1/follow_requests works" 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 @@ test "/api/v1/follow_requests/:id/authorize works" 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -169,7 +169,8 @@ test "it sends to public" 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 @@ test "it doesn't send to user if recipients invalid and thread containment is en
 | 
			
		|||
    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 @@ test "it sends message if recipients invalid and thread containment is disabled"
 | 
			
		|||
    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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -366,7 +366,7 @@ test "follows user", %{conn: conn} 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 @@ test "follows", %{conn: conn} 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue