Merge branch 'feat/chat-list-pagination' into 'develop'
Chats: Introduce /api/v2/pleroma/chats which implements pagination Closes #2140 See merge request pleroma/pleroma!3325
This commit is contained in:
		
						commit
						e7b1f0f5f4
					
				
					 5 changed files with 196 additions and 127 deletions
				
			
		| 
						 | 
				
			
			@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 | 
			
		|||
- **Breaking:** AdminAPI `GET /api/pleroma/admin/users/:nickname_or_id/statuses` changed response format and added the number of total users posts.
 | 
			
		||||
- **Breaking:** AdminAPI `GET /api/pleroma/admin/instances/:instance/statuses` changed response format and added the number of total users posts.
 | 
			
		||||
- Admin API: Reports now ordered by newest
 | 
			
		||||
- Pleroma API: `GET /api/v1/pleroma/chats` is deprecated in favor of `GET /api/v2/pleroma/chats`.
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +59,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 | 
			
		|||
<details>
 | 
			
		||||
  <summary>API Changes</summary>
 | 
			
		||||
- Admin API: (`GET /api/pleroma/admin/users`) filter users by `unconfirmed` status and `actor_type`.
 | 
			
		||||
- Pleroma API: `GET /api/v2/pleroma/chats` added. It is exactly like `GET /api/v1/pleroma/chats` except supports pagination.
 | 
			
		||||
- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
 | 
			
		||||
- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
 | 
			
		||||
- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -131,8 +131,30 @@ def create_operation do
 | 
			
		|||
  def index_operation do
 | 
			
		||||
    %Operation{
 | 
			
		||||
      tags: ["Chats"],
 | 
			
		||||
      summary: "Retrieve list of chats",
 | 
			
		||||
      summary: "Retrieve list of chats (unpaginated)",
 | 
			
		||||
      deprecated: true,
 | 
			
		||||
      description:
 | 
			
		||||
        "Deprecated due to no support for pagination. Using [/api/v2/pleroma/chats](#operation/ChatController.index2) instead is recommended.",
 | 
			
		||||
      operationId: "ChatController.index",
 | 
			
		||||
      parameters: [
 | 
			
		||||
        Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
 | 
			
		||||
      ],
 | 
			
		||||
      responses: %{
 | 
			
		||||
        200 => Operation.response("The chats of the user", "application/json", chats_response())
 | 
			
		||||
      },
 | 
			
		||||
      security: [
 | 
			
		||||
        %{
 | 
			
		||||
          "oAuth" => ["read:chats"]
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def index2_operation do
 | 
			
		||||
    %Operation{
 | 
			
		||||
      tags: ["Chats"],
 | 
			
		||||
      summary: "Retrieve list of chats",
 | 
			
		||||
      operationId: "ChatController.index2",
 | 
			
		||||
      parameters: [
 | 
			
		||||
        Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
 | 
			
		||||
        | pagination_params()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
 | 
			
		|||
 | 
			
		||||
  plug(
 | 
			
		||||
    OAuthScopesPlug,
 | 
			
		||||
    %{scopes: ["read:chats"]} when action in [:messages, :index, :show]
 | 
			
		||||
    %{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show]
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
 | 
			
		||||
| 
						 | 
				
			
			@ -138,18 +138,30 @@ def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do
 | 
			
		||||
  def index(%{assigns: %{user: user}} = conn, params) do
 | 
			
		||||
    chats =
 | 
			
		||||
      index_query(user, params)
 | 
			
		||||
      |> Repo.all()
 | 
			
		||||
 | 
			
		||||
    render(conn, "index.json", chats: chats)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def index2(%{assigns: %{user: user}} = conn, params) do
 | 
			
		||||
    chats =
 | 
			
		||||
      index_query(user, params)
 | 
			
		||||
      |> Pagination.fetch_paginated(params)
 | 
			
		||||
 | 
			
		||||
    render(conn, "index.json", chats: chats)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp index_query(%{id: user_id} = user, params) do
 | 
			
		||||
    exclude_users =
 | 
			
		||||
      User.cached_blocked_users_ap_ids(user) ++
 | 
			
		||||
        if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user)
 | 
			
		||||
 | 
			
		||||
    chats =
 | 
			
		||||
      user_id
 | 
			
		||||
      |> Chat.for_user_query()
 | 
			
		||||
      |> where([c], c.recipient not in ^exclude_users)
 | 
			
		||||
      |> Repo.all()
 | 
			
		||||
 | 
			
		||||
    render(conn, "index.json", chats: chats)
 | 
			
		||||
    user_id
 | 
			
		||||
    |> Chat.for_user_query()
 | 
			
		||||
    |> where([c], c.recipient not in ^exclude_users)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create(%{assigns: %{user: user}} = conn, %{id: id}) do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -420,6 +420,13 @@ defmodule Pleroma.Web.Router do
 | 
			
		|||
    get("/federation_status", InstancesController, :show)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  scope "/api/v2/pleroma", Pleroma.Web.PleromaAPI do
 | 
			
		||||
    scope [] do
 | 
			
		||||
      pipe_through(:authenticated_api)
 | 
			
		||||
      get("/chats", ChatController, :index2)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  scope "/api/v1", Pleroma.Web.MastodonAPI do
 | 
			
		||||
    pipe_through(:authenticated_api)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -304,139 +304,165 @@ test "it returns a chat", %{conn: conn, user: user} do
 | 
			
		|||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "GET /api/v1/pleroma/chats" do
 | 
			
		||||
    setup do: oauth_access(["read:chats"])
 | 
			
		||||
  for tested_endpoint <- ["/api/v1/pleroma/chats", "/api/v2/pleroma/chats"] do
 | 
			
		||||
    describe "GET #{tested_endpoint}" do
 | 
			
		||||
      setup do: oauth_access(["read:chats"])
 | 
			
		||||
 | 
			
		||||
    test "it does not return chats with deleted users", %{conn: conn, user: user} do
 | 
			
		||||
      recipient = insert(:user)
 | 
			
		||||
      {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
 | 
			
		||||
      Pleroma.Repo.delete(recipient)
 | 
			
		||||
      User.invalidate_cache(recipient)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      assert length(result) == 0
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "it does not return chats with users you blocked", %{conn: conn, user: user} do
 | 
			
		||||
      recipient = insert(:user)
 | 
			
		||||
 | 
			
		||||
      {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      assert length(result) == 1
 | 
			
		||||
 | 
			
		||||
      User.block(user, recipient)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      assert length(result) == 0
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "it does not return chats with users you muted", %{conn: conn, user: user} do
 | 
			
		||||
      recipient = insert(:user)
 | 
			
		||||
 | 
			
		||||
      {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      assert length(result) == 1
 | 
			
		||||
 | 
			
		||||
      User.mute(user, recipient)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      assert length(result) == 0
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats?with_muted=true")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      assert length(result) == 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "it returns all chats", %{conn: conn, user: user} do
 | 
			
		||||
      Enum.each(1..30, fn _ ->
 | 
			
		||||
      test "it does not return chats with deleted users", %{conn: conn, user: user} do
 | 
			
		||||
        recipient = insert(:user)
 | 
			
		||||
        {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
        Pleroma.Repo.delete(recipient)
 | 
			
		||||
        User.invalidate_cache(recipient)
 | 
			
		||||
 | 
			
		||||
      assert length(result) == 30
 | 
			
		||||
    end
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get(unquote(tested_endpoint))
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
    test "it return a list of chats the current user is participating in, in descending order of updates",
 | 
			
		||||
         %{conn: conn, user: user} do
 | 
			
		||||
      har = insert(:user)
 | 
			
		||||
      jafnhar = insert(:user)
 | 
			
		||||
      tridi = insert(:user)
 | 
			
		||||
        assert length(result) == 0
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      {:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id)
 | 
			
		||||
      {:ok, chat_1} = time_travel(chat_1, -3)
 | 
			
		||||
      {:ok, chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id)
 | 
			
		||||
      {:ok, _chat_2} = time_travel(chat_2, -2)
 | 
			
		||||
      {:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id)
 | 
			
		||||
      {:ok, chat_3} = time_travel(chat_3, -1)
 | 
			
		||||
      test "it does not return chats with users you blocked", %{conn: conn, user: user} do
 | 
			
		||||
        recipient = insert(:user)
 | 
			
		||||
 | 
			
		||||
      # bump the second one
 | 
			
		||||
      {:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id)
 | 
			
		||||
        {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get(unquote(tested_endpoint))
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      ids = Enum.map(result, & &1["id"])
 | 
			
		||||
        assert length(result) == 1
 | 
			
		||||
 | 
			
		||||
      assert ids == [
 | 
			
		||||
               chat_2.id |> to_string(),
 | 
			
		||||
               chat_3.id |> to_string(),
 | 
			
		||||
               chat_1.id |> to_string()
 | 
			
		||||
             ]
 | 
			
		||||
    end
 | 
			
		||||
        User.block(user, recipient)
 | 
			
		||||
 | 
			
		||||
    test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{
 | 
			
		||||
      conn: conn,
 | 
			
		||||
      user: user
 | 
			
		||||
    } do
 | 
			
		||||
      clear_config([:restrict_unauthenticated, :profiles, :local], true)
 | 
			
		||||
      clear_config([:restrict_unauthenticated, :profiles, :remote], true)
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get(unquote(tested_endpoint))
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
      user2 = insert(:user)
 | 
			
		||||
      user3 = insert(:user, local: false)
 | 
			
		||||
        assert length(result) == 0
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      {:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id)
 | 
			
		||||
      {:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id)
 | 
			
		||||
      test "it does not return chats with users you muted", %{conn: conn, user: user} do
 | 
			
		||||
        recipient = insert(:user)
 | 
			
		||||
 | 
			
		||||
      result =
 | 
			
		||||
        conn
 | 
			
		||||
        |> get("/api/v1/pleroma/chats")
 | 
			
		||||
        |> json_response_and_validate_schema(200)
 | 
			
		||||
        {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
 | 
			
		||||
      account_ids = Enum.map(result, &get_in(&1, ["account", "id"]))
 | 
			
		||||
      assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id])
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get(unquote(tested_endpoint))
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
        assert length(result) == 1
 | 
			
		||||
 | 
			
		||||
        User.mute(user, recipient)
 | 
			
		||||
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get(unquote(tested_endpoint))
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
        assert length(result) == 0
 | 
			
		||||
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get("#{unquote(tested_endpoint)}?with_muted=true")
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
        assert length(result) == 1
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      if tested_endpoint == "/api/v1/pleroma/chats" do
 | 
			
		||||
        test "it returns all chats", %{conn: conn, user: user} do
 | 
			
		||||
          Enum.each(1..30, fn _ ->
 | 
			
		||||
            recipient = insert(:user)
 | 
			
		||||
            {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
          end)
 | 
			
		||||
 | 
			
		||||
          result =
 | 
			
		||||
            conn
 | 
			
		||||
            |> get(unquote(tested_endpoint))
 | 
			
		||||
            |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
          assert length(result) == 30
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        test "it paginates chats", %{conn: conn, user: user} do
 | 
			
		||||
          Enum.each(1..30, fn _ ->
 | 
			
		||||
            recipient = insert(:user)
 | 
			
		||||
            {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
 | 
			
		||||
          end)
 | 
			
		||||
 | 
			
		||||
          result =
 | 
			
		||||
            conn
 | 
			
		||||
            |> get(unquote(tested_endpoint))
 | 
			
		||||
            |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
          assert length(result) == 20
 | 
			
		||||
          last_id = List.last(result)["id"]
 | 
			
		||||
 | 
			
		||||
          result =
 | 
			
		||||
            conn
 | 
			
		||||
            |> get(unquote(tested_endpoint) <> "?max_id=#{last_id}")
 | 
			
		||||
            |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
          assert length(result) == 10
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      test "it return a list of chats the current user is participating in, in descending order of updates",
 | 
			
		||||
           %{conn: conn, user: user} do
 | 
			
		||||
        har = insert(:user)
 | 
			
		||||
        jafnhar = insert(:user)
 | 
			
		||||
        tridi = insert(:user)
 | 
			
		||||
 | 
			
		||||
        {:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id)
 | 
			
		||||
        {:ok, chat_1} = time_travel(chat_1, -3)
 | 
			
		||||
        {:ok, chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id)
 | 
			
		||||
        {:ok, _chat_2} = time_travel(chat_2, -2)
 | 
			
		||||
        {:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id)
 | 
			
		||||
        {:ok, chat_3} = time_travel(chat_3, -1)
 | 
			
		||||
 | 
			
		||||
        # bump the second one
 | 
			
		||||
        {:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id)
 | 
			
		||||
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get(unquote(tested_endpoint))
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
        ids = Enum.map(result, & &1["id"])
 | 
			
		||||
 | 
			
		||||
        assert ids == [
 | 
			
		||||
                 chat_2.id |> to_string(),
 | 
			
		||||
                 chat_3.id |> to_string(),
 | 
			
		||||
                 chat_1.id |> to_string()
 | 
			
		||||
               ]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{
 | 
			
		||||
        conn: conn,
 | 
			
		||||
        user: user
 | 
			
		||||
      } do
 | 
			
		||||
        clear_config([:restrict_unauthenticated, :profiles, :local], true)
 | 
			
		||||
        clear_config([:restrict_unauthenticated, :profiles, :remote], true)
 | 
			
		||||
 | 
			
		||||
        user2 = insert(:user)
 | 
			
		||||
        user3 = insert(:user, local: false)
 | 
			
		||||
 | 
			
		||||
        {:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id)
 | 
			
		||||
        {:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id)
 | 
			
		||||
 | 
			
		||||
        result =
 | 
			
		||||
          conn
 | 
			
		||||
          |> get(unquote(tested_endpoint))
 | 
			
		||||
          |> json_response_and_validate_schema(200)
 | 
			
		||||
 | 
			
		||||
        account_ids = Enum.map(result, &get_in(&1, ["account", "id"]))
 | 
			
		||||
        assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id])
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue