Merge branch 'develop' into openapi/account
authorEgor Kislitsyn <egor@kislitsyn.com>
Wed, 22 Apr 2020 16:18:12 +0000 (20:18 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Wed, 22 Apr 2020 16:18:12 +0000 (20:18 +0400)
1  2 
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
test/web/mastodon_api/controllers/account_controller_test.exs

index d3cebaf056ccfa587a593701992750bb40aa1765,0000000000000000000000000000000000000000..fcf030037a1e182bacebbb30cb4b643588506f83
mode 100644,000000..100644
--- /dev/null
@@@ -1,356 -1,0 +1,356 @@@
-         200 => Operation.response("Account", "application/json", Account)
 +# Pleroma: A lightweight social networking server
 +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 +# SPDX-License-Identifier: AGPL-3.0-only
 +
 +defmodule Pleroma.Web.ApiSpec.AccountOperation do
 +  alias OpenApiSpex.Operation
 +  alias OpenApiSpex.Reference
 +  alias OpenApiSpex.Schema
 +  alias Pleroma.Web.ApiSpec.Schemas.Account
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse
 +  alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
 +  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
 +  alias Pleroma.Web.ApiSpec.Schemas.ListsResponse
 +  alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse
 +  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 +
 +  import Pleroma.Web.ApiSpec.Helpers
 +
 +  @spec open_api_operation(atom) :: Operation.t()
 +  def open_api_operation(action) do
 +    operation = String.to_existing_atom("#{action}_operation")
 +    apply(__MODULE__, operation, [])
 +  end
 +
 +  @spec create_operation() :: Operation.t()
 +  def create_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Register an account",
 +      description:
 +        "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
 +      operationId: "AccountController.create",
 +      requestBody: request_body("Parameters", AccountCreateRequest, required: true),
 +      responses: %{
 +        200 => Operation.response("Account", "application/json", AccountCreateResponse)
 +      }
 +    }
 +  end
 +
 +  def verify_credentials_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      description: "Test to make sure that the user token works.",
 +      summary: "Verify account credentials",
 +      operationId: "AccountController.verify_credentials",
 +      security: [%{"oAuth" => ["read:accounts"]}],
 +      responses: %{
 +        200 => Operation.response("Account", "application/json", Account)
 +      }
 +    }
 +  end
 +
 +  def update_credentials_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Update account credentials",
 +      description: "Update the user's display and preferences.",
 +      operationId: "AccountController.update_credentials",
 +      security: [%{"oAuth" => ["write:accounts"]}],
 +      requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
 +      responses: %{
 +        200 => Operation.response("Account", "application/json", Account)
 +      }
 +    }
 +  end
 +
 +  def relationships_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Check relationships to other accounts",
 +      operationId: "AccountController.relationships",
 +      description: "Find out whether a given account is followed, blocked, muted, etc.",
 +      security: [%{"oAuth" => ["read:follows"]}],
 +      parameters: [
 +        Operation.parameter(
 +          :id,
 +          :query,
 +          %Schema{
 +            oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
 +          },
 +          "Account IDs",
 +          example: "123"
 +        )
 +      ],
 +      responses: %{
 +        200 => Operation.response("Account", "application/json", AccountRelationshipsResponse)
 +      }
 +    }
 +  end
 +
 +  def show_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Account",
 +      operationId: "AccountController.show",
 +      description: "View information about a profile.",
 +      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
 +      responses: %{
 +        200 => Operation.response("Account", "application/json", Account)
 +      }
 +    }
 +  end
 +
 +  def statuses_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Statuses",
 +      operationId: "AccountController.statuses",
 +      description:
 +        "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
 +      parameters:
 +        [
 +          %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
 +          Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
 +          Operation.parameter(:tagged, :query, :string, "With tag"),
 +          Operation.parameter(
 +            :only_media,
 +            :query,
 +            BooleanLike,
 +            "Include only statuses with media attached"
 +          ),
 +          Operation.parameter(
 +            :with_muted,
 +            :query,
 +            BooleanLike,
 +            "Include statuses from muted acccounts."
 +          ),
 +          Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
 +          Operation.parameter(
 +            :exclude_visibilities,
 +            :query,
 +            %Schema{type: :array, items: VisibilityScope},
 +            "Exclude visibilities"
 +          )
 +        ] ++ pagination_params(),
 +      responses: %{
 +        200 => Operation.response("Statuses", "application/json", StatusesResponse)
 +      }
 +    }
 +  end
 +
 +  def followers_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Followers",
 +      operationId: "AccountController.followers",
 +      security: [%{"oAuth" => ["read:accounts"]}],
 +      description:
 +        "Accounts which follow the given account, if network is not hidden by the account owner.",
 +      parameters:
 +        [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
 +      responses: %{
 +        200 => Operation.response("Accounts", "application/json", AccountsResponse)
 +      }
 +    }
 +  end
 +
 +  def following_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Following",
 +      operationId: "AccountController.following",
 +      security: [%{"oAuth" => ["read:accounts"]}],
 +      description:
 +        "Accounts which the given account is following, if network is not hidden by the account owner.",
 +      parameters:
 +        [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
 +      responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)}
 +    }
 +  end
 +
 +  def lists_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Lists containing this account",
 +      operationId: "AccountController.lists",
 +      security: [%{"oAuth" => ["read:lists"]}],
 +      description: "User lists that you have added this account to.",
 +      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
 +      responses: %{200 => Operation.response("Lists", "application/json", ListsResponse)}
 +    }
 +  end
 +
 +  def follow_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Follow",
 +      operationId: "AccountController.follow",
 +      security: [%{"oAuth" => ["follow", "write:follows"]}],
 +      description: "Follow the given account",
 +      parameters: [
 +        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
 +        Operation.parameter(
 +          :reblogs,
 +          :query,
 +          BooleanLike,
 +          "Receive this account's reblogs in home timeline? Defaults to true."
 +        )
 +      ],
 +      responses: %{
 +        200 => Operation.response("Relationship", "application/json", AccountRelationship)
 +      }
 +    }
 +  end
 +
 +  def unfollow_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Unfollow",
 +      operationId: "AccountController.unfollow",
 +      security: [%{"oAuth" => ["follow", "write:follows"]}],
 +      description: "Unfollow the given account",
 +      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
 +      responses: %{
 +        200 => Operation.response("Relationship", "application/json", AccountRelationship)
 +      }
 +    }
 +  end
 +
 +  def mute_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Mute",
 +      operationId: "AccountController.mute",
 +      security: [%{"oAuth" => ["follow", "write:mutes"]}],
 +      requestBody: request_body("Parameters", AccountMuteRequest),
 +      description:
 +        "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
 +      parameters: [
 +        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
 +        Operation.parameter(
 +          :notifications,
 +          :query,
 +          %Schema{allOf: [BooleanLike], default: true},
 +          "Mute notifications in addition to statuses? Defaults to `true`."
 +        )
 +      ],
 +      responses: %{
 +        200 => Operation.response("Relationship", "application/json", AccountRelationship)
 +      }
 +    }
 +  end
 +
 +  def unmute_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Unmute",
 +      operationId: "AccountController.unmute",
 +      security: [%{"oAuth" => ["follow", "write:mutes"]}],
 +      description: "Unmute the given account.",
 +      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
 +      responses: %{
 +        200 => Operation.response("Relationship", "application/json", AccountRelationship)
 +      }
 +    }
 +  end
 +
 +  def block_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Block",
 +      operationId: "AccountController.block",
 +      security: [%{"oAuth" => ["follow", "write:blocks"]}],
 +      description:
 +        "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
 +      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
 +      responses: %{
 +        200 => Operation.response("Relationship", "application/json", AccountRelationship)
 +      }
 +    }
 +  end
 +
 +  def unblock_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Unblock",
 +      operationId: "AccountController.unblock",
 +      security: [%{"oAuth" => ["follow", "write:blocks"]}],
 +      description: "Unblock the given account.",
 +      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
 +      responses: %{
 +        200 => Operation.response("Relationship", "application/json", AccountRelationship)
 +      }
 +    }
 +  end
 +
 +  def follows_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Follows",
 +      operationId: "AccountController.follows",
 +      security: [%{"oAuth" => ["follow", "write:follows"]}],
 +      requestBody: request_body("Parameters", AccountFollowsRequest, required: true),
 +      responses: %{
++        200 => Operation.response("Account", "application/json", AccountRelationship)
 +      }
 +    }
 +  end
 +
 +  def mutes_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Muted accounts",
 +      operationId: "AccountController.mutes",
 +      description: "Accounts the user has muted.",
 +      security: [%{"oAuth" => ["follow", "read:mutes"]}],
 +      responses: %{
 +        200 => Operation.response("Accounts", "application/json", AccountsResponse)
 +      }
 +    }
 +  end
 +
 +  def blocks_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Blocked users",
 +      operationId: "AccountController.blocks",
 +      description: "View your blocks. See also accounts/:id/{block,unblock}",
 +      security: [%{"oAuth" => ["read:blocks"]}],
 +      responses: %{
 +        200 => Operation.response("Accounts", "application/json", AccountsResponse)
 +      }
 +    }
 +  end
 +
 +  def endorsements_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Endorsements",
 +      operationId: "AccountController.endorsements",
 +      description: "Not implemented",
 +      security: [%{"oAuth" => ["read:accounts"]}],
 +      responses: %{
 +        200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
 +      }
 +    }
 +  end
 +
 +  def identity_proofs_operation do
 +    %Operation{
 +      tags: ["accounts"],
 +      summary: "Identity proofs",
 +      operationId: "AccountController.identity_proofs",
 +      description: "Not implemented",
 +      responses: %{
 +        200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
 +      }
 +    }
 +  end
 +end
index 2c774b694906ddb882aa76d83be04b1cdb485bf3,5a92cebd806880d26d97e2c2e851c7cbbfb1c039..93df7964523307a65a18a7cd710e0da636f6a1ae
@@@ -322,11 -293,11 +322,11 @@@ defmodule Pleroma.Web.MastodonAPI.Accou
  
    @doc "POST /api/v1/accounts/:id/follow"
    def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
-     {:error, :not_found}
+     {:error, "Can not follow yourself"}
    end
  
 -  def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
 -    with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
 +  def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
 +    with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
        render(conn, "relationship.json", user: follower, target: followed)
      else
        {:error, message} -> json_response(conn, :forbidden, %{error: message})
    end
  
    @doc "POST /api/v1/follows"
-   def follows(%{assigns: %{user: follower}, body_params: %{uri: uri}} = conn, _) do
-     with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
-          {_, true} <- {:followed, follower.id != followed.id},
-          {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
-       render(conn, "show.json", user: followed, for: follower)
-     else
-       {:followed, _} -> {:error, :not_found}
-       {:error, message} -> json_response(conn, :forbidden, %{error: message})
 -  def follows(conn, %{"uri" => uri}) do
++  def follows(%{body_params: %{uri: uri}} = conn, _) do
+     case User.get_cached_by_nickname(uri) do
+       %User{} = user ->
+         conn
+         |> assign(:account, user)
+         |> follow(%{})
+       nil ->
+         {:error, :not_found}
      end
    end
  
index 32a9d85a8e35bbe08e05381b9e7ed3d17a792973,8c428efeee0c0114b89b39e5c0c1955751449b6b..d885b5e08b0cc07a8bde1bddff1d71d93f1c4d98
@@@ -731,13 -690,8 +731,13 @@@ defmodule Pleroma.Web.MastodonAPI.Accou
  
        # self follow via uri
        user = User.get_cached_by_id(user.id)
 -      conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
 +
 +      conn_res =
 +        conn
 +        |> put_req_header("content-type", "multipart/form-data")
 +        |> post("/api/v1/follows", %{"uri" => user.nickname})
 +
-       assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+       assert %{"error" => "Can not follow yourself"} = json_response(conn_res, 400)
  
        # follow non existing user
        conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")