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:
Haelwenn 2022-08-12 01:34:36 +00:00
commit 93f12c0d0d
5 changed files with 323 additions and 0 deletions

View File

@ -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",
}
}
```

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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