Merge remote-tracking branch 'pleroma/develop' into feature/disable-account
authorEgor Kislitsyn <egor@kislitsyn.com>
Tue, 14 May 2019 11:15:56 +0000 (18:15 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Tue, 14 May 2019 11:15:56 +0000 (18:15 +0700)
1  2 
config/config.exs
lib/pleroma/activity.ex
lib/pleroma/user.ex
lib/pleroma/user/query.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api.ex
test/user_test.exs

Simple merge
Simple merge
index 10ee01b8c1307388c9cb354f2d226b879bd84c5f,474de9ba5c7ec716c99fd0ed4ec55c878dd36fba..cf378d46772d73f41bee44e5dcbb3547e3f488a1
@@@ -117,23 -117,6 +115,20 @@@ defmodule Pleroma.User d
      }
    end
  
-   def following_count(%User{following: following, id: id}) do
-     from(u in User,
-       where: u.follower_address in ^following,
-       where: u.id != ^id
-     )
-     |> restrict_deactivated()
 +  defp restrict_deactivated(query) do
 +    from(u in query,
 +      where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
 +    )
 +  end
 +
 +  def following_count(%User{following: []}), do: 0
 +
++  def following_count(%User{} = user) do
++    user
++    |> get_friends_query()
 +    |> Repo.aggregate(:count, :id)
 +  end
 +
    def remote_user_creation(params) do
      params =
        params
      candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
  
      autofollowed_users =
-       from(u in User,
-         where: u.local == true,
-         where: u.nickname in ^candidates
-       )
 -      User.Query.build(%{nickname: candidates, local: true})
++      User.Query.build(%{nickname: candidates, local: true, deactivated: false})
        |> Repo.all()
  
      follow_all(user, autofollowed_users)
      )
    end
  
-   def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do
-     from(
-       u in User,
-       where: fragment("? <@ ?", ^[follower_address], u.following),
-       where: u.id != ^id
-     )
-     |> restrict_deactivated()
+   @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
+   def get_followers_query(%User{} = user, nil) do
 -    User.Query.build(%{followers: user})
++    User.Query.build(%{followers: user, deactivated: false})
    end
  
    def get_followers_query(user, page) do
      Repo.all(from(u in q, select: u.id))
    end
  
-   def get_friends_query(%User{id: id, following: following}, nil) do
-     from(
-       u in User,
-       where: u.follower_address in ^following,
-       where: u.id != ^id
-     )
-     |> restrict_deactivated()
+   @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
+   def get_friends_query(%User{} = user, nil) do
 -    User.Query.build(%{friends: user})
++    User.Query.build(%{friends: user, deactivated: false})
    end
  
    def get_friends_query(user, page) do
  
    def update_follower_count(%User{} = user) do
      follower_count_query =
-       User
-       |> where([u], ^user.follower_address in u.following)
-       |> where([u], u.id != ^user.id)
 -      User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)})
++      User.Query.build(%{followers: user, deactivated: false})
 +      |> select([u], %{count: count(u.id)})
-       |> restrict_deactivated()
  
      User
      |> where(id: ^user.id)
      end
    end
  
-   def get_users_from_set_query(ap_ids, false) do
-     from(
-       u in User,
-       where: u.ap_id in ^ap_ids
-     )
-   end
-   def get_users_from_set_query(ap_ids, true) do
-     query = get_users_from_set_query(ap_ids, false)
-     from(
-       u in query,
-       where: u.local == true
-     )
-   end
+   @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
    def get_users_from_set(ap_ids, local_only \\ true) do
-     get_users_from_set_query(ap_ids, local_only)
 -    criteria = %{ap_id: ap_ids}
++    criteria = %{ap_id: ap_ids, deactivated: false}
+     criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
+     User.Query.build(criteria)
      |> Repo.all()
    end
  
+   @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
    def get_recipients_from_activity(%Activity{recipients: to}) do
-     query =
-       from(
-         u in User,
-         where: u.ap_id in ^to,
-         or_where: fragment("? && ?", u.following, ^to)
-       )
-     query = from(u in query, where: u.local == true)
-     Repo.all(query)
 -    User.Query.build(%{recipients_from_activity: to, local: true})
++    User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
+     |> Repo.all()
    end
  
    def search(query, resolve \\ false, for_user \\ nil) do
      end
    end
  
-   def muted_users(user),
-     do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
+   @spec muted_users(User.t()) :: [User.t()]
+   def muted_users(user) do
 -    User.Query.build(%{ap_id: user.info.mutes})
++    User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
+     |> Repo.all()
+   end
  
-   def blocked_users(user),
-     do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
+   @spec blocked_users(User.t()) :: [User.t()]
+   def blocked_users(user) do
 -    User.Query.build(%{ap_id: user.info.blocks})
++    User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
+     |> Repo.all()
+   end
  
-   def subscribers(user),
-     do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers))
+   @spec subscribers(User.t()) :: [User.t()]
+   def subscribers(user) do
 -    User.Query.build(%{ap_id: user.info.subscribers})
++    User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
+     |> Repo.all()
+   end
  
    def block_domain(user, domain) do
      info_cng =
      update_and_set_cache(cng)
    end
  
-   def maybe_local_user_query(query, local) do
-     if local, do: local_user_query(query), else: query
-   end
-   def local_user_query(query \\ User) do
-     from(
-       u in query,
-       where: u.local == true,
-       where: not is_nil(u.nickname)
-     )
-   end
-   def maybe_external_user_query(query, external) do
-     if external, do: external_user_query(query), else: query
-   end
-   def external_user_query(query \\ User) do
-     from(
-       u in query,
-       where: u.local == false,
-       where: not is_nil(u.nickname)
-     )
-   end
-   def maybe_active_user_query(query, active) do
-     if active, do: active_user_query(query), else: query
-   end
-   def active_user_query(query \\ User) do
-     from(
-       u in query,
-       where: fragment("not (?->'deactivated' @> 'true')", u.info),
-       where: not is_nil(u.nickname)
-     )
-   end
-   def maybe_deactivated_user_query(query, deactivated) do
-     if deactivated, do: deactivated_user_query(query), else: query
-   end
-   def deactivated_user_query(query \\ User) do
-     from(
-       u in query,
-       where: fragment("(?->'deactivated' @> 'true')", u.info),
-       where: not is_nil(u.nickname)
-     )
-   end
-   def active_local_user_query do
-     from(
-       u in local_user_query(),
-       where: fragment("not (?->'deactivated' @> 'true')", u.info)
-     )
-   end
-   def moderator_user_query do
-     from(
-       u in User,
-       where: u.local == true,
-       where: fragment("?->'is_moderator' @> 'true'", u.info)
-     )
-   end
 +  def deactivate_async(user, status \\ true) do
-     PleromaJobQueue.enqueue(:user, __MODULE__, [:deactivate_async, user, status])
++    PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status])
 +  end
 +
 +  def perform(:deactivate_async, user, status), do: deactivate(user, status)
 +
    def deactivate(%User{} = user, status \\ true) do
      info_cng = User.Info.set_activation_status(user.info, status)
  
      }
    end
  
+   @spec all_superusers() :: [User.t()]
    def all_superusers do
-     from(
-       u in User,
-       where: u.local == true,
-       where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
-     )
 -    User.Query.build(%{super_users: true, local: true})
++    User.Query.build(%{super_users: true, local: true, deactivated: false})
      |> Repo.all()
    end
  
index 0000000000000000000000000000000000000000,2dfe5ce92c4b154099890d15c0220884d3a7d4a5..3873ef80c15c007a208c54c18fcf8c0475c5337f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,150 +1,156 @@@
 -  defp compose_query({:deactivated, _}, query) do
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+ defmodule Pleroma.User.Query do
+   @moduledoc """
+   User query builder module. Builds query from new query or another user query.
+     ## Example:
+         query = Pleroma.User.Query(%{nickname: "nickname"})
+         another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"})
+         Pleroma.Repo.all(query)
+         Pleroma.Repo.all(another_query)
+   Adding new rules:
+     - *ilike criteria*
+       - add field to @ilike_criteria list
+       - pass non empty string
+       - e.g. Pleroma.User.Query.build(%{nickname: "nickname"})
+     - *equal criteria*
+       - add field to @equal_criteria list
+       - pass non empty string
+       - e.g. Pleroma.User.Query.build(%{email: "email@example.com"})
+     - *contains criteria*
+       - add field to @containns_criteria list
+       - pass values list
+       - e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]})
+   """
+   import Ecto.Query
+   import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
+   alias Pleroma.User
+   @type criteria ::
+           %{
+             query: String.t(),
+             tags: [String.t()],
+             name: String.t(),
+             email: String.t(),
+             local: boolean(),
+             external: boolean(),
+             active: boolean(),
+             deactivated: boolean(),
+             is_admin: boolean(),
+             is_moderator: boolean(),
+             super_users: boolean(),
+             followers: User.t(),
+             friends: User.t(),
+             recipients_from_activity: [String.t()],
+             nickname: [String.t()],
+             ap_id: [String.t()]
+           }
+           | %{}
+   @ilike_criteria [:nickname, :name, :query]
+   @equal_criteria [:email]
+   @role_criteria [:is_admin, :is_moderator]
+   @contains_criteria [:ap_id, :nickname]
+   @spec build(criteria()) :: Query.t()
+   def build(query \\ base_query(), criteria) do
+     prepare_query(query, criteria)
+   end
+   @spec paginate(Ecto.Query.t(), pos_integer(), pos_integer()) :: Ecto.Query.t()
+   def paginate(query, page, page_size) do
+     from(u in query,
+       limit: ^page_size,
+       offset: ^((page - 1) * page_size)
+     )
+   end
+   defp base_query do
+     from(u in User)
+   end
+   defp prepare_query(query, criteria) do
+     Enum.reduce(criteria, query, &compose_query/2)
+   end
+   defp compose_query({key, value}, query)
+        when key in @ilike_criteria and not_empty_string(value) do
+     # hack for :query key
+     key = if key == :query, do: :nickname, else: key
+     where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
+   end
+   defp compose_query({key, value}, query)
+        when key in @equal_criteria and not_empty_string(value) do
+     where(query, [u], ^[{key, value}])
+   end
+   defp compose_query({key, values}, query) when key in @contains_criteria and is_list(values) do
+     where(query, [u], field(u, ^key) in ^values)
+   end
+   defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do
+     Enum.reduce(tags, query, &prepare_tag_criteria/2)
+   end
+   defp compose_query({key, _}, query) when key in @role_criteria do
+     where(query, [u], fragment("(?->? @> 'true')", u.info, ^to_string(key)))
+   end
+   defp compose_query({:super_users, _}, query) do
+     where(
+       query,
+       [u],
+       fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
+     )
+   end
+   defp compose_query({:local, _}, query), do: location_query(query, true)
+   defp compose_query({:external, _}, query), do: location_query(query, false)
+   defp compose_query({:active, _}, query) do
+     where(query, [u], fragment("not (?->'deactivated' @> 'true')", u.info))
+     |> where([u], not is_nil(u.nickname))
+   end
++  defp compose_query({:deactivated, false}, query) do
++    from(u in query,
++      where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
++    )
++  end
++
++  defp compose_query({:deactivated, true}, query) do
+     where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
+     |> 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))
+     |> where([u], u.id != ^id)
+   end
+   defp compose_query({:friends, %User{id: id, following: following}}, query) do
+     where(query, [u], u.follower_address in ^following)
+     |> where([u], u.id != ^id)
+   end
+   defp compose_query({:recipients_from_activity, to}, query) do
+     where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
+   end
+   defp compose_query(_unsupported_param, query), do: query
+   defp prepare_tag_criteria(tag, query) do
+     or_where(query, [u], fragment("? = any(?)", ^tag, u.tags))
+   end
+   defp location_query(query, local) do
+     where(query, [u], u.local == ^local)
+     |> where([u], not is_nil(u.nickname))
+   end
+ end
Simple merge
index 00c06dfaac6a5c2689a849ebbc51e89ed76dcaab,60de0206e551552594cd59d05f827a9ebc072112..0b65e89e9bdceaffe3aa86f2477935c5ac6a209a
@@@ -817,71 -816,13 +817,73 @@@ defmodule Pleroma.UserTest d
      assert addressed in recipients
    end
  
 -  test ".deactivate can de-activate then re-activate a user" do
 -    user = insert(:user)
 -    assert false == user.info.deactivated
 -    {:ok, user} = User.deactivate(user)
 -    assert true == user.info.deactivated
 -    {:ok, user} = User.deactivate(user, false)
 -    assert false == user.info.deactivated
 +  describe ".deactivate" do
 +    test "can de-activate then re-activate a user" do
 +      user = insert(:user)
 +      assert false == user.info.deactivated
 +      {:ok, user} = User.deactivate(user)
 +      assert true == user.info.deactivated
 +      {:ok, user} = User.deactivate(user, false)
 +      assert false == user.info.deactivated
 +    end
 +
 +    test "hide a user from followers " do
 +      user = insert(:user)
 +      user2 = insert(:user)
 +
 +      {:ok, user} = User.follow(user, user2)
 +      {:ok, _user} = User.deactivate(user)
 +
 +      info = User.get_cached_user_info(user2)
 +
 +      assert info.follower_count == 0
 +      assert {:ok, []} = User.get_followers(user2)
 +    end
 +
 +    test "hide a user from friends" do
 +      user = insert(:user)
 +      user2 = insert(:user)
 +
 +      {:ok, user2} = User.follow(user2, user)
 +      assert User.following_count(user2) == 1
 +
 +      {:ok, _user} = User.deactivate(user)
 +
 +      info = User.get_cached_user_info(user2)
 +
 +      assert info.following_count == 0
 +      assert User.following_count(user2) == 0
 +      assert {:ok, []} = User.get_friends(user2)
 +    end
 +
 +    test "hide a user's statuses from timelines and notifications" do
 +      user = insert(:user)
 +      user2 = insert(:user)
 +
 +      {:ok, user2} = User.follow(user2, user)
 +
 +      {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}"})
 +
++      activity = Repo.preload(activity, :bookmark)
++
 +      [notification] = Pleroma.Notification.for_user(user2)
 +      assert notification.activity.id == activity.id
 +
-       assert [activity] == ActivityPub.fetch_public_activities(%{})
++      assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
 +
 +      assert [activity] ==
 +               ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
 +               |> ActivityPub.contain_timeline(user2)
 +
 +      {:ok, _user} = User.deactivate(user)
 +
 +      assert [] == ActivityPub.fetch_public_activities(%{})
 +      assert [] == Pleroma.Notification.for_user(user2)
 +
 +      assert [] ==
 +               ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
 +               |> ActivityPub.contain_timeline(user2)
 +    end
    end
  
    test ".delete_user_activities deletes all create activities" do