Merge branch 'feature/add-pagination-to-users-admin-api' into 'develop'
authorkaniini <nenolod@gmail.com>
Sun, 3 Mar 2019 15:59:15 +0000 (15:59 +0000)
committerkaniini <nenolod@gmail.com>
Sun, 3 Mar 2019 15:59:15 +0000 (15:59 +0000)
Add pagination and search to users

See merge request pleroma/pleroma!873

1  2 
lib/pleroma/user.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
lib/pleroma/web/twitter_api/views/user_view.ex
test/user_test.exs

diff --combined lib/pleroma/user.ex
index d69ca094e7f5821fad457caa3b8db1c738e102af,230155c3375f24b9f054b8b397ac9f77ea04a5c8..78543b4265b24430b12845b9b16eb5ca3e6bd99c
@@@ -547,11 -547,8 +547,8 @@@ defmodule Pleroma.User d
    end
  
    def get_followers_query(user, page) do
-     from(
-       u in get_followers_query(user, nil),
-       limit: 20,
-       offset: ^((page - 1) * 20)
-     )
+     from(u in get_followers_query(user, nil))
+     |> paginate(page, 20)
    end
  
    def get_followers_query(user), do: get_followers_query(user, nil)
    end
  
    def get_friends_query(user, page) do
-     from(
-       u in get_friends_query(user, nil),
-       limit: 20,
-       offset: ^((page - 1) * 20)
-     )
+     from(u in get_friends_query(user, nil))
+     |> paginate(page, 20)
    end
  
    def get_friends_query(user), do: get_friends_query(user, nil)
          ),
        where:
          fragment(
 -          "? @> ?",
 +          "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
            a.data,
 -          ^%{"object" => user.ap_id}
 +          a.data,
 +          ^user.ap_id
          )
      )
    end
  
 -  def update_follow_request_count(%User{} = user) do
 -    subquery =
 +  def get_follow_requests(%User{} = user) do
 +    users =
        user
        |> User.get_follow_requests_query()
 -      |> select([a], %{count: count(a.id)})
 +      |> join(:inner, [a], u in User, 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()
  
 +    {:ok, users}
 +  end
 +
 +  def increase_note_count(%User{} = user) do
      User
      |> where(id: ^user.id)
 -    |> join(:inner, [u], s in subquery(subquery))
 -    |> update([u, s],
 +    |> update([u],
        set: [
          info:
            fragment(
 -            "jsonb_set(?, '{follow_request_count}', ?::varchar::jsonb, true)",
 +            "jsonb_set(?, '{note_count}', ((?->>'note_count')::int + 1)::varchar::jsonb, true)",
              u.info,
 -            s.count
 +            u.info
            )
        ]
      )
      |> Repo.update_all([], returning: true)
      |> case do
 -      {1, [user]} -> {:ok, user}
 +      {1, [user]} -> set_cache(user)
        _ -> {:error, user}
      end
    end
  
 -  def get_follow_requests(%User{} = user) do
 -    q = get_follow_requests_query(user)
 -    reqs = Repo.all(q)
 -
 -    users =
 -      Enum.map(reqs, fn req -> req.actor end)
 -      |> Enum.uniq()
 -      |> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
 -      |> Enum.filter(fn u -> !is_nil(u) end)
 -      |> Enum.filter(fn u -> !following?(u, user) end)
 -
 -    {:ok, users}
 -  end
 -
 -  def increase_note_count(%User{} = user) do
 -    info_cng = User.Info.add_to_note_count(user.info, 1)
 -
 -    cng =
 -      change(user)
 -      |> put_embed(:info, info_cng)
 -
 -    update_and_set_cache(cng)
 -  end
 -
    def decrease_note_count(%User{} = user) do
 -    info_cng = User.Info.add_to_note_count(user.info, -1)
 -
 -    cng =
 -      change(user)
 -      |> put_embed(:info, info_cng)
 -
 -    update_and_set_cache(cng)
 +    User
 +    |> where(id: ^user.id)
 +    |> update([u],
 +      set: [
 +        info:
 +          fragment(
 +            "jsonb_set(?, '{note_count}', (greatest(0, (?->>'note_count')::int - 1))::varchar::jsonb, true)",
 +            u.info,
 +            u.info
 +          )
 +      ]
 +    )
 +    |> Repo.update_all([], returning: true)
 +    |> case do
 +      {1, [user]} -> set_cache(user)
 +      _ -> {:error, user}
 +    end
    end
  
    def update_note_count(%User{} = user) do
  
    def update_follower_count(%User{} = user) do
      follower_count_query =
 -      from(
 -        u in User,
 -        where: ^user.follower_address in u.following,
 -        where: u.id != ^user.id,
 -        select: count(u.id)
 -      )
 -
 -    follower_count = Repo.one(follower_count_query)
 -
 -    info_cng =
 -      user.info
 -      |> User.Info.set_follower_count(follower_count)
 -
 -    cng =
 -      change(user)
 -      |> put_embed(:info, info_cng)
 +      User
 +      |> where([u], ^user.follower_address in u.following)
 +      |> where([u], u.id != ^user.id)
 +      |> select([u], %{count: count(u.id)})
  
 -    update_and_set_cache(cng)
 +    User
 +    |> where(id: ^user.id)
 +    |> join(:inner, [u], s in subquery(follower_count_query))
 +    |> update([u, s],
 +      set: [
 +        info:
 +          fragment(
 +            "jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
 +            u.info,
 +            s.count
 +          )
 +      ]
 +    )
 +    |> Repo.update_all([], returning: true)
 +    |> case do
 +      {1, [user]} -> set_cache(user)
 +      _ -> {:error, user}
 +    end
    end
  
    def get_users_from_set_query(ap_ids, false) do
      Repo.all(query)
    end
  
+   @spec search_for_admin(binary(), %{
+           admin: Pleroma.User.t(),
+           local: boolean(),
+           page: number(),
+           page_size: number()
+         }) :: {:ok, [Pleroma.User.t()], number()}
+   def search_for_admin(term, %{admin: admin, local: local, page: page, page_size: page_size}) do
+     term = String.trim_leading(term, "@")
+     local_paginated_query =
+       User
+       |> maybe_local_user_query(local)
+       |> paginate(page, page_size)
+     search_query = fts_search_subquery(term, local_paginated_query)
+     count =
+       term
+       |> fts_search_subquery()
+       |> maybe_local_user_query(local)
+       |> Repo.aggregate(:count, :id)
+     {:ok, do_search(search_query, admin), count}
+   end
+   @spec all_for_admin(number(), number()) :: {:ok, [Pleroma.User.t()], number()}
+   def all_for_admin(page, page_size) do
+     query = from(u in User, order_by: u.id)
+     paginated_query =
+       query
+       |> paginate(page, page_size)
+     count =
+       query
+       |> Repo.aggregate(:count, :id)
+     {:ok, Repo.all(paginated_query), count}
+   end
    def search(query, resolve \\ false, for_user \\ nil) do
      # Strip the beginning @ off if there is a query
      query = String.trim_leading(query, "@")
      Enum.uniq_by(fts_results ++ trigram_results, & &1.id)
    end
  
-   def all_except_one(user) do
-     query = from(u in User, where: u.id != ^user.id)
-     Repo.all(query)
-   end
    defp do_search(subquery, for_user, options \\ []) do
      q =
        from(
      boost_search_results(results, for_user)
    end
  
-   defp fts_search_subquery(query) do
+   defp fts_search_subquery(term, query \\ User) do
      processed_query =
-       query
+       term
        |> String.replace(~r/\W+/, " ")
        |> String.trim()
        |> String.split()
        |> Enum.join(" | ")
  
      from(
-       u in User,
+       u in query,
        select_merge: %{
          search_rank:
            fragment(
      )
    end
  
-   defp trigram_search_subquery(query) do
+   defp trigram_search_subquery(term) do
      from(
        u in User,
        select_merge: %{
          search_rank:
            fragment(
              "similarity(?, trim(? || ' ' || coalesce(?, '')))",
-             ^query,
+             ^term,
              u.nickname,
              u.name
            )
        },
-       where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^query)
+       where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
      )
    end
  
      update_and_set_cache(cng)
    end
  
 +  def mutes?(nil, _), do: false
    def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
  
    def blocks?(user, %{ap_id: ap_id}) do
      update_and_set_cache(cng)
    end
  
-   def local_user_query do
+   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 User,
+       u in query,
        where: u.local == true,
        where: not is_nil(u.nickname)
      )
      )
      |> Repo.all()
    end
+   defp paginate(query, page, page_size) do
+     from(u in query,
+       limit: ^page_size,
+       offset: ^((page - 1) * page_size)
+     )
+   end
  end
index 41e3acc60e7c57db2a7d3228b5de78fb8b876e9f,19264a93f37ad78b61c90449ff02c7127f1deb2d..de7b9f24ca29d986395dc4523ce97c0df5cfb5ce
@@@ -167,7 -167,6 +167,7 @@@ defmodule Pleroma.Web.TwitterAPI.Contro
        params
        |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
        |> Map.put("blocking_user", user)
 +      |> Map.put(:visibility, ~w[unlisted public private])
  
      activities = ActivityPub.fetch_activities([user.ap_id], params)
  
    end
  
    def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
-     users = User.search(query, true, user)
+     users = User.search(query, resolve: true, for_user: user)
  
      conn
      |> put_view(UserView)
index e72ce977c2d30a8f024e8c5fc0092c46b2899081,df73844761af4cfb3e02044ca8b8da7ef827e6af..0791ed7608e1be3b3318d63a456f067d4d41ea02
@@@ -9,7 -9,6 +9,6 @@@ defmodule Pleroma.Web.TwitterAPI.UserVi
    alias Pleroma.User
    alias Pleroma.Web.CommonAPI.Utils
    alias Pleroma.Web.MediaProxy
-   alias Pleroma.Web.TwitterAPI.UserView
  
    def render("show.json", %{user: user = %User{}} = assigns) do
      render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns)
        else: %{}
    end
  
-   def render("index_for_admin.json", %{users: users} = opts) do
-     users
-     |> render_many(UserView, "show_for_admin.json", opts)
-   end
-   def render("show_for_admin.json", %{user: user}) do
-     %{
-       "id" => user.id,
-       "nickname" => user.nickname,
-       "deactivated" => user.info.deactivated
-     }
-   end
    def render("short.json", %{
          user: %User{
            nickname: nickname,
            "confirmation_pending" => user_info.confirmation_pending,
            "tags" => user.tags
          }
 -        |> maybe_with_follow_request_count(user, for_user)
 +        |> maybe_with_activation_status(user, for_user)
      }
  
      data =
      end
    end
  
 -  defp maybe_with_follow_request_count(data, %User{id: id, info: %{locked: true}} = user, %User{
 -         id: id
 -       }) do
 -    Map.put(data, "follow_request_count", user.info.follow_request_count)
 +  defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
 +    Map.put(data, "deactivated", user.info.deactivated)
    end
  
 -  defp maybe_with_follow_request_count(data, _, _), do: data
 +  defp maybe_with_activation_status(data, _, _), do: data
  
    defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
      Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role})
diff --combined test/user_test.exs
index b8d41ecfd8407da1a416b2ce4e230b9ef8db02aa,188ab1a5ca44554612bc477203e69a97f1dd120a..390e3ef1392b2e3f37015ca36c7fc0270f61d318
@@@ -50,34 -50,6 +50,34 @@@ defmodule Pleroma.UserTest d
      assert expected_followers_collection == User.ap_followers(user)
    end
  
 +  test "returns all pending follow requests" do
 +    unlocked = insert(:user)
 +    locked = insert(:user, %{info: %{locked: true}})
 +    follower = insert(:user)
 +
 +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => unlocked.id})
 +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => locked.id})
 +
 +    assert {:ok, []} = User.get_follow_requests(unlocked)
 +    assert {:ok, [activity]} = User.get_follow_requests(locked)
 +
 +    assert activity
 +  end
 +
 +  test "doesn't return already accepted or duplicate follow requests" do
 +    locked = insert(:user, %{info: %{locked: true}})
 +    pending_follower = insert(:user)
 +    accepted_follower = insert(:user)
 +
 +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
 +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
 +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id})
 +    User.maybe_follow(accepted_follower, locked)
 +
 +    assert {:ok, [activity]} = User.get_follow_requests(locked)
 +    assert activity
 +  end
 +
    test "follow_all follows mutliple users" do
      user = insert(:user)
      followed_zero = insert(:user)
        {:ok, follower} = User.follow(follower, u1)
        {:ok, u1} = User.follow(u1, friend)
  
-       assert [friend.id, follower.id, u2.id] == Enum.map(User.search("doe", false, u1), & &1.id)
+       assert [friend.id, follower.id, u2.id] --
+                Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []
      end
  
      test "finds a user whose name is nil" do
      end
  
      test "works with URIs" do
-       results = User.search("http://mastodon.example.org/users/admin", true)
+       results = User.search("http://mastodon.example.org/users/admin", resolve: true)
        result = results |> List.first()
  
        user = User.get_by_ap_id("http://mastodon.example.org/users/admin")