Merge branch 'from/upstream-develop/tusooa/sync-settings' into 'develop'
Synchronized settings for apps (frontends) See merge request pleroma/pleroma!3698
This commit is contained in:
commit
93f12c0d0d
|
@ -725,3 +725,42 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
|
|||
* Authentication: required
|
||||
* Params: none
|
||||
* Response: HTTP 200 on success, 500 on error
|
||||
|
||||
## `/api/v1/pleroma/settings/:app`
|
||||
### Gets settings for some application
|
||||
* Method `GET`
|
||||
* Authentication: `read:accounts`
|
||||
|
||||
* Response: JSON. The settings for that application, or empty object if there is none.
|
||||
* Example response:
|
||||
```json
|
||||
{
|
||||
"some key": "some value"
|
||||
}
|
||||
```
|
||||
|
||||
### Updates settings for some application
|
||||
* Method `PATCH`
|
||||
* Authentication: `write:accounts`
|
||||
* Request body: JSON object. The object will be merged recursively with old settings. If some field is set to null, it is removed.
|
||||
* Example request:
|
||||
```json
|
||||
{
|
||||
"some key": "some value",
|
||||
"key to remove": null,
|
||||
"nested field": {
|
||||
"some key": "some value",
|
||||
"key to remove": null
|
||||
}
|
||||
}
|
||||
```
|
||||
* Response: JSON. Updated (merged) settings for that application.
|
||||
* Example response:
|
||||
```json
|
||||
{
|
||||
"some key": "some value",
|
||||
"nested field": {
|
||||
"some key": "some value",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaSettingsOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
def show_operation do
|
||||
%Operation{
|
||||
tags: ["Settings"],
|
||||
summary: "Get settings for an application",
|
||||
description: "Get synchronized settings for an application",
|
||||
operationId: "SettingsController.show",
|
||||
parameters: [app_name_param()],
|
||||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
responses: %{
|
||||
200 => Operation.response("object", "application/json", object())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_operation do
|
||||
%Operation{
|
||||
tags: ["Settings"],
|
||||
summary: "Update settings for an application",
|
||||
description: "Update synchronized settings for an application",
|
||||
operationId: "SettingsController.update",
|
||||
parameters: [app_name_param()],
|
||||
security: [%{"oAuth" => ["write:accounts"]}],
|
||||
requestBody: request_body("Parameters", update_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("object", "application/json", object())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def app_name_param do
|
||||
Operation.parameter(:app, :path, %Schema{type: :string}, "Application name",
|
||||
example: "pleroma-fe",
|
||||
required: true
|
||||
)
|
||||
end
|
||||
|
||||
def object do
|
||||
%Schema{
|
||||
title: "Settings object",
|
||||
description: "The object that contains settings for the application.",
|
||||
type: :object
|
||||
}
|
||||
end
|
||||
|
||||
def update_request do
|
||||
%Schema{
|
||||
title: "SettingsUpdateRequest",
|
||||
type: :object,
|
||||
description:
|
||||
"The settings object to be merged with the current settings. To remove a field, set it to null.",
|
||||
example: %{
|
||||
"config1" => true,
|
||||
"config2_to_unset" => nil
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.SettingsController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:accounts"]} when action in [:update]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:accounts"]} when action in [:show]
|
||||
)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaSettingsOperation
|
||||
|
||||
@doc "GET /api/v1/pleroma/settings/:app"
|
||||
def show(%{assigns: %{user: user}} = conn, %{app: app} = _params) do
|
||||
conn
|
||||
|> json(get_settings(user, app))
|
||||
end
|
||||
|
||||
@doc "PATCH /api/v1/pleroma/settings/:app"
|
||||
def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{app: app} = _params) do
|
||||
settings =
|
||||
get_settings(user, app)
|
||||
|> merge_recursively(body_params)
|
||||
|
||||
with changeset <-
|
||||
Pleroma.User.update_changeset(
|
||||
user,
|
||||
%{pleroma_settings_store: %{app => settings}}
|
||||
),
|
||||
{:ok, _} <- Pleroma.Repo.update(changeset) do
|
||||
conn
|
||||
|> json(settings)
|
||||
end
|
||||
end
|
||||
|
||||
defp merge_recursively(old, %{} = new) do
|
||||
old = ensure_object(old)
|
||||
|
||||
Enum.reduce(
|
||||
new,
|
||||
old,
|
||||
fn
|
||||
{k, nil}, acc ->
|
||||
Map.drop(acc, [k])
|
||||
|
||||
{k, %{} = new_child}, acc ->
|
||||
Map.put(acc, k, merge_recursively(acc[k], new_child))
|
||||
|
||||
{k, v}, acc ->
|
||||
Map.put(acc, k, v)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
defp get_settings(user, app) do
|
||||
user.pleroma_settings_store
|
||||
|> Map.get(app, %{})
|
||||
|> ensure_object()
|
||||
end
|
||||
|
||||
defp ensure_object(%{} = object) do
|
||||
object
|
||||
end
|
||||
|
||||
defp ensure_object(_) do
|
||||
%{}
|
||||
end
|
||||
end
|
|
@ -463,6 +463,13 @@ defmodule Pleroma.Web.Router do
|
|||
get("/birthdays", AccountController, :birthdays)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
get("/settings/:app", SettingsController, :show)
|
||||
patch("/settings/:app", SettingsController, :update)
|
||||
end
|
||||
|
||||
post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.SettingsControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "GET /api/v1/pleroma/settings/:app" do
|
||||
setup do
|
||||
oauth_access(["read:accounts"])
|
||||
end
|
||||
|
||||
test "it gets empty settings", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> get("/api/v1/pleroma/settings/pleroma-fe")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert response == %{}
|
||||
end
|
||||
|
||||
test "it gets settings", %{conn: conn, user: user} do
|
||||
response =
|
||||
conn
|
||||
|> assign(
|
||||
:user,
|
||||
struct(user,
|
||||
pleroma_settings_store: %{
|
||||
"pleroma-fe" => %{
|
||||
"foo" => "bar"
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|> get("/api/v1/pleroma/settings/pleroma-fe")
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert %{"foo" => "bar"} == response
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/v1/pleroma/settings/:app" do
|
||||
setup do
|
||||
settings = %{
|
||||
"foo" => "bar",
|
||||
"nested" => %{
|
||||
"1" => "2"
|
||||
}
|
||||
}
|
||||
|
||||
user =
|
||||
insert(
|
||||
:user,
|
||||
%{
|
||||
pleroma_settings_store: %{
|
||||
"pleroma-fe" => settings
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
%{conn: conn} = oauth_access(["write:accounts"], user: user)
|
||||
|
||||
%{conn: conn, user: user, settings: settings}
|
||||
end
|
||||
|
||||
test "it adds keys", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> patch("/api/v1/pleroma/settings/pleroma-fe", %{
|
||||
"foo" => "edited",
|
||||
"bar" => "new",
|
||||
"nested" => %{"3" => "4"}
|
||||
})
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert response == %{
|
||||
"foo" => "edited",
|
||||
"bar" => "new",
|
||||
"nested" => %{
|
||||
"1" => "2",
|
||||
"3" => "4"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
test "it removes keys", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> patch("/api/v1/pleroma/settings/pleroma-fe", %{
|
||||
"foo" => nil,
|
||||
"bar" => nil,
|
||||
"nested" => %{
|
||||
"1" => nil,
|
||||
"3" => nil
|
||||
}
|
||||
})
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert response == %{
|
||||
"nested" => %{}
|
||||
}
|
||||
end
|
||||
|
||||
test "it does not override settings for other apps", %{
|
||||
conn: conn,
|
||||
user: user,
|
||||
settings: settings
|
||||
} do
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> patch("/api/v1/pleroma/settings/admin-fe", %{"foo" => "bar"})
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
user = Pleroma.User.get_by_id(user.id)
|
||||
|
||||
assert user.pleroma_settings_store == %{
|
||||
"pleroma-fe" => settings,
|
||||
"admin-fe" => %{"foo" => "bar"}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue