diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index 35cf4c0d8..7348dcbee 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -4,7 +4,7 @@
defmodule Pleroma.Web.ApiSpec.Helpers do
def request_body(description, schema_ref, opts \\ []) do
- media_types = ["application/json", "multipart/form-data"]
+ media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
content =
media_types
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 3d2270c29..d7b56cc2b 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+ alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
@spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do
@@ -44,7 +45,18 @@ def verify_credentials_operation do
end
def update_credentials_operation do
- :ok
+ %Operation{
+ tags: ["accounts"],
+ summary: "Update account credentials",
+ description: "Update the user's display and preferences.",
+ operationId: "AccountController.update_credentials",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ requestBody:
+ Helpers.request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account)
+ }
+ }
end
def relationships_operation do
diff --git a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
new file mode 100644
index 000000000..fbbdf95f5
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AccountAttributeField do
+ alias OpenApiSpex.Schema
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "AccountAttributeField",
+ description: "Request schema for account custom fields",
+ type: :object,
+ properties: %{
+ name: %Schema{type: :string},
+ value: %Schema{type: :string}
+ },
+ required: [:name, :value],
+ example: %{
+ "JSON" => %{
+ "name" => "Website",
+ "value" => "https://pleroma.com"
+ }
+ }
+ })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
new file mode 100644
index 000000000..a50bce5ed
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
@@ -0,0 +1,123 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.AccountAttributeField
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "AccountUpdateCredentialsRequest",
+ description: "POST body for creating an account",
+ type: :object,
+ properties: %{
+ bot: %Schema{
+ type: :boolean,
+ description: "Whether the account has a bot flag."
+ },
+ display_name: %Schema{
+ type: :string,
+ description: "The display name to use for the profile."
+ },
+ note: %Schema{type: :string, description: "The account bio."},
+ avatar: %Schema{
+ type: :string,
+ description: "Avatar image encoded using multipart/form-data",
+ format: :binary
+ },
+ header: %Schema{
+ type: :string,
+ description: "Header image encoded using multipart/form-data",
+ format: :binary
+ },
+ locked: %Schema{
+ type: :boolean,
+ description: "Whether manual approval of follow requests is required."
+ },
+ fields_attributes: %Schema{
+ oneOf: [%Schema{type: :array, items: AccountAttributeField}, %Schema{type: :object}]
+ },
+ # NOTE: `source` field is not supported
+ #
+ # source: %Schema{
+ # type: :object,
+ # properties: %{
+ # privacy: %Schema{type: :string},
+ # sensitive: %Schema{type: :boolean},
+ # language: %Schema{type: :string}
+ # }
+ # },
+
+ # Pleroma-specific fields
+ no_rich_text: %Schema{
+ type: :boolean,
+ description: "html tags are stripped from all statuses requested from the API"
+ },
+ hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"},
+ hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"},
+ hide_followers_count: %Schema{
+ type: :boolean,
+ description: "user's follower count will be hidden"
+ },
+ hide_follows_count: %Schema{
+ type: :boolean,
+ description: "user's follow count will be hidden"
+ },
+ hide_favorites: %Schema{
+ type: :boolean,
+ description: "user's favorites timeline will be hidden"
+ },
+ show_role: %Schema{
+ type: :boolean,
+ description: "user's role (e.g admin, moderator) will be exposed to anyone in the
+ API"
+ },
+ default_scope: %Schema{
+ type: :string,
+ description: "The scope returned under privacy key in Source subentity"
+ },
+ pleroma_settings_store: %Schema{
+ type: :object,
+ description: "Opaque user settings to be saved on the backend."
+ },
+ skip_thread_containment: %Schema{
+ type: :boolean,
+ description: "Skip filtering out broken threads"
+ },
+ allow_following_move: %Schema{
+ type: :boolean,
+ description: "Allows automatically follow moved following accounts"
+ },
+ pleroma_background_image: %Schema{
+ type: :string,
+ description: "Sets the background image of the user.",
+ format: :binary
+ },
+ discoverable: %Schema{
+ type: :boolean,
+ description: "Discovery of this account in search results and other services is allowed."
+ },
+ actor_type: %Schema{type: :string, description: "the type of this account."}
+ },
+ example: %{
+ bot: false,
+ display_name: "cofe",
+ note: "foobar",
+ fields_attributes: [%{name: "foo", value: "bar"}],
+ no_rich_text: false,
+ hide_followers: true,
+ hide_follows: false,
+ hide_followers_count: false,
+ hide_follows_count: false,
+ hide_favorites: false,
+ show_role: false,
+ default_scope: "private",
+ pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
+ skip_thread_containment: false,
+ allow_following_move: false,
+ discoverable: false,
+ actor_type: "Person"
+ }
+ })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index eb082daf8..9c986b3b2 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -82,7 +82,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(
OpenApiSpex.Plug.CastAndValidate,
- [render_error: Pleroma.Web.ApiSpec.RenderError] when action == :create
+ [render_error: Pleroma.Web.ApiSpec.RenderError]
+ when action in [:create, :verify_credentials, :update_credentials]
)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -152,9 +153,15 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
end
@doc "PATCH /api/v1/accounts/update_credentials"
- def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
+ def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do
user = original_user
+ params =
+ params
+ |> Map.from_struct()
+ |> Enum.filter(fn {_, value} -> not is_nil(value) end)
+ |> Enum.into(%{})
+
user_params =
[
:no_rich_text,
@@ -170,22 +177,22 @@ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
:discoverable
]
|> Enum.reduce(%{}, fn key, acc ->
- add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
+ add_if_present(acc, params, key, key, &{:ok, truthy_param?(&1)})
end)
- |> add_if_present(params, "display_name", :name)
- |> add_if_present(params, "note", :bio)
- |> add_if_present(params, "avatar", :avatar)
- |> add_if_present(params, "header", :banner)
- |> add_if_present(params, "pleroma_background_image", :background)
+ |> add_if_present(params, :display_name, :name)
+ |> add_if_present(params, :note, :bio)
+ |> add_if_present(params, :avatar, :avatar)
+ |> add_if_present(params, :header, :banner)
+ |> add_if_present(params, :pleroma_background_image, :background)
|> add_if_present(
params,
- "fields_attributes",
+ :fields_attributes,
:raw_fields,
&{:ok, normalize_fields_attributes(&1)}
)
- |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store)
- |> add_if_present(params, "default_scope", :default_scope)
- |> add_if_present(params, "actor_type", :actor_type)
+ |> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store)
+ |> add_if_present(params, :default_scope, :default_scope)
+ |> add_if_present(params, :actor_type, :actor_type)
changeset = User.update_changeset(user, user_params)
@@ -200,7 +207,7 @@ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
with true <- Map.has_key?(params, params_field),
- {:ok, new_value} <- value_function.(params[params_field]) do
+ {:ok, new_value} <- value_function.(Map.get(params, params_field)) do
Map.put(map, map_field, new_value)
else
_ -> map
@@ -211,7 +218,13 @@ defp normalize_fields_attributes(fields) do
if Enum.all?(fields, &is_tuple/1) do
Enum.map(fields, fn {_, v} -> v end)
else
- fields
+ Enum.map(fields, fn
+ %Pleroma.Web.ApiSpec.Schemas.AccountAttributeField{} = field ->
+ %{"name" => field.name, "value" => field.value}
+
+ field ->
+ field
+ end)
end
end
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 064874201..36ce372c2 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -51,6 +51,11 @@ defp oauth_access(scopes, opts \\ []) do
%{user: user, token: token, conn: conn}
end
+ defp request_content_type(%{conn: conn}) do
+ conn = put_req_header(conn, "content-type", "multipart/form-data")
+ [conn: conn]
+ end
+
defp ensure_federating_or_authenticated(conn, url, user) do
initial_setting = Config.get([:instance, :federating])
on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
index 37501b8cc..a54059074 100644
--- a/test/web/api_spec/account_operation_test.exs
+++ b/test/web/api_spec/account_operation_test.exs
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+ alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
import OpenApiSpex.TestAssertions
import Pleroma.Factory
@@ -31,6 +32,12 @@ test "AccountCreateResponse example matches schema" do
assert_schema(schema.example, "AccountCreateResponse", api_spec)
end
+ test "AccountUpdateCredentialsRequest example matches schema" do
+ api_spec = ApiSpec.spec()
+ schema = AccountUpdateCredentialsRequest.schema()
+ assert_schema(schema.example, "AccountUpdateCredentialsRequest", api_spec)
+ end
+
test "AccountController produces a AccountCreateResponse", %{conn: conn} do
api_spec = ApiSpec.spec()
app_token = insert(:oauth_token, user: nil)
@@ -52,4 +59,29 @@ test "AccountController produces a AccountCreateResponse", %{conn: conn} do
assert_schema(json, "AccountCreateResponse", api_spec)
end
+
+ test "AccountUpdateCredentialsRequest produces an Account", %{conn: conn} do
+ api_spec = ApiSpec.spec()
+ token = insert(:oauth_token, scopes: ["read", "write"])
+
+ json =
+ conn
+ |> put_req_header("authorization", "Bearer " <> token.token)
+ |> put_req_header("content-type", "application/json")
+ |> patch(
+ "/api/v1/accounts/update_credentials",
+ %{
+ hide_followers_count: "true",
+ hide_follows_count: "true",
+ skip_thread_containment: "true",
+ hide_follows: "true",
+ pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
+ note: "foobar",
+ fields_attributes: [%{name: "foo", value: "bar"}]
+ }
+ )
+ |> json_response(200)
+
+ assert_schema(json, "Account", api_spec)
+ end
end
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index 2d256f63c..0e890a980 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
describe "updating credentials" do
setup do: oauth_access(["write:accounts"])
+ setup :request_content_type
test "sets user settings in a generic way", %{conn: conn} do
res_conn =
@@ -237,6 +238,7 @@ test "requires 'write:accounts' permission" do
for token <- [token1, token2] do
conn =
build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
|> put_req_header("authorization", "Bearer #{token.token}")
|> patch("/api/v1/accounts/update_credentials", %{})