From a0123459b32981c29dc8ba974404eed50029c86d Mon Sep 17 00:00:00 2001
From: Chocobozzz <me@florianbigard.com>
Date: Fri, 1 Mar 2019 11:41:28 +0100
Subject: [PATCH 1/3] Add ability to list users

---
 lib/mobilizon/actors/actors.ex                |  26 +++--
 lib/mobilizon/ecto.ex                         |  10 ++
 lib/mobilizon_web/resolvers/user.ex           |  14 +++
 lib/mobilizon_web/schema/sort.ex              |   9 ++
 lib/mobilizon_web/schema/user.ex              |  24 ++++
 test/mobilizon/actors/actors_test.exs         |   2 +-
 .../resolvers/user_resolver_test.exs          | 110 +++++++++++++++++-
 7 files changed, 182 insertions(+), 13 deletions(-)
 create mode 100644 lib/mobilizon_web/schema/sort.ex

diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex
index 181e61137..62f9cc98a 100644
--- a/lib/mobilizon/actors/actors.ex
+++ b/lib/mobilizon/actors/actors.ex
@@ -266,8 +266,21 @@ defmodule Mobilizon.Actors do
       [%Mobilizon.Actors.User{}]
 
   """
-  def list_users do
-    Repo.all(User)
+  def list_users(page \\ nil, limit \\ nil, sort \\ nil, direction \\ nil) do
+    Repo.all(
+      User
+      |> paginate(page, limit)
+      |> sort(sort, direction)
+    )
+  end
+
+  def count_users() do
+    Repo.one(
+      from(
+        u in User,
+        select: count(u.id)
+      )
+    )
   end
 
   def insert_or_update_actor(data, preload \\ false) do
@@ -300,15 +313,6 @@ defmodule Mobilizon.Actors do
   #    update_and_set_cache(cs)
   #  end
 
-  def count_users() do
-    Repo.one(
-      from(
-        u in User,
-        select: count(u.id)
-      )
-    )
-  end
-
   @doc """
   Gets a single user.
 
diff --git a/lib/mobilizon/ecto.ex b/lib/mobilizon/ecto.ex
index b5ee0d4f7..66fb2ec72 100644
--- a/lib/mobilizon/ecto.ex
+++ b/lib/mobilizon/ecto.ex
@@ -19,6 +19,16 @@ defmodule Mobilizon.Ecto do
     )
   end
 
+  @doc """
+  Add sort to the query
+  """
+  def sort(query, sort, direction) do
+    from(
+      query,
+      order_by: [{^direction, ^sort}]
+    )
+  end
+
   def increment_slug(slug) do
     case List.pop_at(String.split(slug, "-"), -1) do
       {nil, _} ->
diff --git a/lib/mobilizon_web/resolvers/user.ex b/lib/mobilizon_web/resolvers/user.ex
index 12594c6a6..3b3786803 100644
--- a/lib/mobilizon_web/resolvers/user.ex
+++ b/lib/mobilizon_web/resolvers/user.ex
@@ -24,6 +24,20 @@ defmodule MobilizonWeb.Resolvers.User do
     {:error, "You need to be logged-in to view current user"}
   end
 
+  @doc """
+  List instance users
+  """
+  def list_and_count_users(
+        _parent,
+        %{page: page, limit: limit, sort: sort, direction: direction},
+        _resolution
+      ) do
+    total = Task.async(&Actors.count_users/0)
+    elements = Task.async(fn -> Actors.list_users(page, limit, sort, direction) end)
+
+    {:ok, %{total: Task.await(total), elements: Task.await(elements)}}
+  end
+
   @doc """
   Login an user. Returns a token and the user
   """
diff --git a/lib/mobilizon_web/schema/sort.ex b/lib/mobilizon_web/schema/sort.ex
new file mode 100644
index 000000000..15584044b
--- /dev/null
+++ b/lib/mobilizon_web/schema/sort.ex
@@ -0,0 +1,9 @@
+defmodule MobilizonWeb.Schema.SortType do
+  use Absinthe.Schema.Notation
+
+  @desc "Available sort directions"
+  enum :sort_direction do
+    value(:asc)
+    value(:desc)
+  end
+end
diff --git a/lib/mobilizon_web/schema/user.ex b/lib/mobilizon_web/schema/user.ex
index c0eba4562..ebbc73815 100644
--- a/lib/mobilizon_web/schema/user.ex
+++ b/lib/mobilizon_web/schema/user.ex
@@ -6,6 +6,8 @@ defmodule MobilizonWeb.Schema.UserType do
   alias MobilizonWeb.Resolvers.User
   import MobilizonWeb.Schema.Utils
 
+  import_types(MobilizonWeb.Schema.SortType)
+
   @desc "A local user of Mobilizon"
   object :user do
     field(:id, non_null(:id), description: "The user's ID")
@@ -36,6 +38,17 @@ defmodule MobilizonWeb.Schema.UserType do
     )
   end
 
+  @desc "Users list"
+  object :users do
+    field(:total, non_null(:integer), description: "Total elements")
+    field(:elements, non_null(list_of(:user)), description: "User elements")
+  end
+
+  @desc "The list of possible options for the event's status"
+  enum :sortable_user_field do
+    value(:id)
+  end
+
   object :user_queries do
     @desc "Get an user"
     field :user, :user do
@@ -47,6 +60,17 @@ defmodule MobilizonWeb.Schema.UserType do
     field :logged_user, :user do
       resolve(&User.get_current_user/3)
     end
+
+    @desc "List instance users"
+    field :users, :users do
+      arg(:page, :integer, default_value: 1)
+      arg(:limit, :integer, default_value: 10)
+
+      arg(:sort, :sortable_user_field, default_value: :id)
+      arg(:direction, :sort_direction, default_value: :asc)
+
+      resolve(&User.list_and_count_users/3)
+    end
   end
 
   object :user_mutations do
diff --git a/test/mobilizon/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs
index 30948c724..b4c3fabba 100644
--- a/test/mobilizon/actors/actors_test.exs
+++ b/test/mobilizon/actors/actors_test.exs
@@ -274,7 +274,7 @@ defmodule Mobilizon.ActorsTest do
 
     test "list_users/0 returns all users" do
       user = insert(:user)
-      users = Actors.list_users()
+      users = Actors.list_users(nil, nil, :id, :desc)
       assert [user.id] == users |> Enum.map(& &1.id)
     end
 
diff --git a/test/mobilizon_web/resolvers/user_resolver_test.exs b/test/mobilizon_web/resolvers/user_resolver_test.exs
index e0cbd4525..2fcfd5c87 100644
--- a/test/mobilizon_web/resolvers/user_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/user_resolver_test.exs
@@ -72,6 +72,114 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
     end
   end
 
+  describe "Resolver: List users" do
+    test "list_users/3 returns a list of users", context do
+      insert(:user, email: "riri@example.com")
+      insert(:user, email: "fifi@example.com")
+      insert(:user, email: "loulou@example.com")
+
+      query = """
+      {
+        users {
+          total,
+          elements {
+            email
+          }
+        }
+      }
+      """
+
+      res =
+        context.conn
+        |> get("/api", AbsintheHelpers.query_skeleton(query, "user"))
+
+      assert json_response(res, 200)["errors"] == nil
+      assert json_response(res, 200)["data"]["users"]["total"] == 3
+      assert json_response(res, 200)["data"]["users"]["elements"] |> length == 3
+
+      assert json_response(res, 200)["data"]["users"]["elements"]
+             |> Enum.map(& &1["email"]) == [
+               "riri@example.com",
+               "fifi@example.com",
+               "loulou@example.com"
+             ]
+
+      query = """
+      {
+        users(page: 2, limit: 1) {
+          total,
+          elements {
+            email
+          }
+        }
+      }
+      """
+
+      res =
+        context.conn
+        |> get("/api", AbsintheHelpers.query_skeleton(query, "user"))
+
+      assert json_response(res, 200)["errors"] == nil
+      assert json_response(res, 200)["data"]["users"]["total"] == 3
+      assert json_response(res, 200)["data"]["users"]["elements"] |> length == 1
+
+      assert json_response(res, 200)["data"]["users"]["elements"] |> Enum.map(& &1["email"]) == [
+               "fifi@example.com"
+             ]
+
+      query = """
+      {
+        users(page: 3, limit: 1, sort: ID, direction: DESC) {
+          total,
+          elements {
+            email
+          }
+        }
+      }
+      """
+
+      res =
+        context.conn
+        |> get("/api", AbsintheHelpers.query_skeleton(query, "user"))
+
+      assert json_response(res, 200)["errors"] == nil
+      assert json_response(res, 200)["data"]["users"]["total"] == 3
+      assert json_response(res, 200)["data"]["users"]["elements"] |> length == 1
+
+      assert json_response(res, 200)["data"]["users"]["elements"] |> Enum.map(& &1["email"]) == [
+               "riri@example.com"
+             ]
+    end
+
+    test "get_current_user/3 returns the current logged-in user", context do
+      user = insert(:user)
+
+      query = """
+      {
+          loggedUser {
+            id
+          }
+        }
+      """
+
+      res =
+        context.conn
+        |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_user"))
+
+      assert json_response(res, 200)["data"]["loggedUser"] == nil
+
+      assert hd(json_response(res, 200)["errors"])["message"] ==
+               "You need to be logged-in to view current user"
+
+      res =
+        context.conn
+        |> auth_conn(user)
+        |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_user"))
+
+      assert json_response(res, 200)["data"]["loggedUser"]["id"] == to_string(user.id)
+    end
+  end
+
   describe "Resolver: Create an user & actor" do
     @user_creation %{
       email: "test@demo.tld",
@@ -490,7 +598,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
               ) {
                 token,
                 user {
-                  id  
+                  id
                 }
               }
             }

From f08b6334f0c36d6ba7c69bb050c88038addd2ae9 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Tue, 5 Mar 2019 10:30:50 +0100
Subject: [PATCH 2/3] Add @moduledoc to new module

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/mobilizon_web/schema/sort.ex | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lib/mobilizon_web/schema/sort.ex b/lib/mobilizon_web/schema/sort.ex
index 15584044b..2f44e786e 100644
--- a/lib/mobilizon_web/schema/sort.ex
+++ b/lib/mobilizon_web/schema/sort.ex
@@ -1,4 +1,7 @@
 defmodule MobilizonWeb.Schema.SortType do
+  @moduledoc """
+  Allows sorting a collection of elements
+  """
   use Absinthe.Schema.Notation
 
   @desc "Available sort directions"

From 9c98f34b5ca57fee454dcda7007b4f9585bcda0d Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Tue, 5 Mar 2019 12:14:31 +0100
Subject: [PATCH 3/3] Invert list users sort direction (make it desc)

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/mobilizon_web/schema/user.ex                    | 2 +-
 test/mobilizon_web/resolvers/user_resolver_test.exs | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/mobilizon_web/schema/user.ex b/lib/mobilizon_web/schema/user.ex
index ebbc73815..24ec29ffa 100644
--- a/lib/mobilizon_web/schema/user.ex
+++ b/lib/mobilizon_web/schema/user.ex
@@ -67,7 +67,7 @@ defmodule MobilizonWeb.Schema.UserType do
       arg(:limit, :integer, default_value: 10)
 
       arg(:sort, :sortable_user_field, default_value: :id)
-      arg(:direction, :sort_direction, default_value: :asc)
+      arg(:direction, :sort_direction, default_value: :desc)
 
       resolve(&User.list_and_count_users/3)
     end
diff --git a/test/mobilizon_web/resolvers/user_resolver_test.exs b/test/mobilizon_web/resolvers/user_resolver_test.exs
index 2fcfd5c87..f38e866b2 100644
--- a/test/mobilizon_web/resolvers/user_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/user_resolver_test.exs
@@ -99,9 +99,9 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
 
       assert json_response(res, 200)["data"]["users"]["elements"]
              |> Enum.map(& &1["email"]) == [
-               "riri@example.com",
+               "loulou@example.com",
                "fifi@example.com",
-               "loulou@example.com"
+               "riri@example.com"
              ]
 
       query = """