Move config actions to AdminAPI.ConfigController
authorEgor Kislitsyn <egor@kislitsyn.com>
Thu, 28 May 2020 18:23:15 +0000 (22:23 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Thu, 28 May 2020 18:23:15 +0000 (22:23 +0400)
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
lib/pleroma/web/admin_api/controllers/config_controller.ex [new file with mode: 0644]
lib/pleroma/web/router.ex
test/web/admin_api/controllers/admin_api_controller_test.exs
test/web/admin_api/controllers/config_controller_test.exs [new file with mode: 0644]

index 783203c071028fcb885bb5f3b056e9237b8300e0..52900026f85cb5375faa5530deecc9babc2a7e0a 100644 (file)
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   alias Pleroma.Activity
   alias Pleroma.Config
-  alias Pleroma.ConfigDB
   alias Pleroma.MFA
   alias Pleroma.ModerationLog
   alias Pleroma.Plugs.OAuthScopesPlug
@@ -24,7 +23,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.AdminAPI
   alias Pleroma.Web.AdminAPI.AccountView
-  alias Pleroma.Web.AdminAPI.ConfigView
   alias Pleroma.Web.AdminAPI.ModerationLogView
   alias Pleroma.Web.AdminAPI.Report
   alias Pleroma.Web.AdminAPI.ReportView
@@ -38,7 +36,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   require Logger
 
-  @descriptions Pleroma.Docs.JSON.compile()
   @users_page_size 50
 
   plug(
@@ -105,11 +102,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     OAuthScopesPlug,
     %{scopes: ["read"], admin: true}
     when action in [
-           :config_show,
            :list_log,
            :stats,
            :relay_list,
-           :config_descriptions,
            :need_reboot
          ]
   )
@@ -119,7 +114,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     %{scopes: ["write"], admin: true}
     when action in [
            :restart,
-           :config_update,
            :resend_confirmation_email,
            :confirm_email,
            :oauth_app_create,
@@ -821,105 +815,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> render("index.json", %{log: log})
   end
 
-  def config_descriptions(conn, _params) do
-    descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
-
-    json(conn, descriptions)
-  end
-
-  def config_show(conn, %{"only_db" => true}) do
-    with :ok <- configurable_from_database() do
-      configs = Pleroma.Repo.all(ConfigDB)
-
-      conn
-      |> put_view(ConfigView)
-      |> render("index.json", %{configs: configs})
-    end
-  end
-
-  def config_show(conn, _params) do
-    with :ok <- configurable_from_database() do
-      configs = ConfigDB.get_all_as_keyword()
-
-      merged =
-        Config.Holder.default_config()
-        |> ConfigDB.merge(configs)
-        |> Enum.map(fn {group, values} ->
-          Enum.map(values, fn {key, value} ->
-            db =
-              if configs[group][key] do
-                ConfigDB.get_db_keys(configs[group][key], key)
-              end
-
-            db_value = configs[group][key]
-
-            merged_value =
-              if !is_nil(db_value) and Keyword.keyword?(db_value) and
-                   ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
-                ConfigDB.merge_group(group, key, value, db_value)
-              else
-                value
-              end
-
-            setting = %{
-              group: ConfigDB.convert(group),
-              key: ConfigDB.convert(key),
-              value: ConfigDB.convert(merged_value)
-            }
-
-            if db, do: Map.put(setting, :db, db), else: setting
-          end)
-        end)
-        |> List.flatten()
-
-      json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
-    end
-  end
-
-  def config_update(conn, %{"configs" => configs}) do
-    with :ok <- configurable_from_database() do
-      {_errors, results} =
-        configs
-        |> Enum.filter(&whitelisted_config?/1)
-        |> Enum.map(fn
-          %{"group" => group, "key" => key, "delete" => true} = params ->
-            ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
-
-          %{"group" => group, "key" => key, "value" => value} ->
-            ConfigDB.update_or_create(%{group: group, key: key, value: value})
-        end)
-        |> Enum.split_with(fn result -> elem(result, 0) == :error end)
-
-      {deleted, updated} =
-        results
-        |> Enum.map(fn {:ok, config} ->
-          Map.put(config, :db, ConfigDB.get_db_keys(config))
-        end)
-        |> Enum.split_with(fn config ->
-          Ecto.get_meta(config, :state) == :deleted
-        end)
-
-      Config.TransferTask.load_and_update_env(deleted, false)
-
-      if !Restarter.Pleroma.need_reboot?() do
-        changed_reboot_settings? =
-          (updated ++ deleted)
-          |> Enum.any?(fn config ->
-            group = ConfigDB.from_string(config.group)
-            key = ConfigDB.from_string(config.key)
-            value = ConfigDB.from_binary(config.value)
-            Config.TransferTask.pleroma_need_restart?(group, key, value)
-          end)
-
-        if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
-      end
-
-      conn
-      |> put_view(ConfigView)
-      |> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()})
-    end
-  end
-
   def restart(conn, _params) do
     with :ok <- configurable_from_database() do
       Restarter.Pleroma.restart(Config.get(:env), 50)
@@ -940,28 +835,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  defp whitelisted_config?(group, key) do
-    if whitelisted_configs = Config.get(:database_config_whitelist) do
-      Enum.any?(whitelisted_configs, fn
-        {whitelisted_group} ->
-          group == inspect(whitelisted_group)
-
-        {whitelisted_group, whitelisted_key} ->
-          group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
-      end)
-    else
-      true
-    end
-  end
-
-  defp whitelisted_config?(%{"group" => group, "key" => key}) do
-    whitelisted_config?(group, key)
-  end
-
-  defp whitelisted_config?(%{:group => group} = config) do
-    whitelisted_config?(group, config[:key])
-  end
-
   def reload_emoji(conn, _params) do
     Pleroma.Emoji.reload()
 
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
new file mode 100644 (file)
index 0000000..7429809
--- /dev/null
@@ -0,0 +1,150 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ConfigController do
+  use Pleroma.Web, :controller
+
+  alias Pleroma.Config
+  alias Pleroma.ConfigDB
+  alias Pleroma.Plugs.OAuthScopesPlug
+
+  @descriptions Pleroma.Docs.JSON.compile()
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["read"], admin: true}
+    when action in [:show, :descriptions]
+  )
+
+  plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
+
+  action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+  def descriptions(conn, _params) do
+    descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
+
+    json(conn, descriptions)
+  end
+
+  def show(conn, %{"only_db" => true}) do
+    with :ok <- configurable_from_database() do
+      configs = Pleroma.Repo.all(ConfigDB)
+      render(conn, "index.json", %{configs: configs})
+    end
+  end
+
+  def show(conn, _params) do
+    with :ok <- configurable_from_database() do
+      configs = ConfigDB.get_all_as_keyword()
+
+      merged =
+        Config.Holder.default_config()
+        |> ConfigDB.merge(configs)
+        |> Enum.map(fn {group, values} ->
+          Enum.map(values, fn {key, value} ->
+            db =
+              if configs[group][key] do
+                ConfigDB.get_db_keys(configs[group][key], key)
+              end
+
+            db_value = configs[group][key]
+
+            merged_value =
+              if not is_nil(db_value) and Keyword.keyword?(db_value) and
+                   ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
+                ConfigDB.merge_group(group, key, value, db_value)
+              else
+                value
+              end
+
+            setting = %{
+              group: ConfigDB.convert(group),
+              key: ConfigDB.convert(key),
+              value: ConfigDB.convert(merged_value)
+            }
+
+            if db, do: Map.put(setting, :db, db), else: setting
+          end)
+        end)
+        |> List.flatten()
+
+      json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()})
+    end
+  end
+
+  def update(conn, %{"configs" => configs}) do
+    with :ok <- configurable_from_database() do
+      results =
+        configs
+        |> Enum.filter(&whitelisted_config?/1)
+        |> Enum.map(fn
+          %{"group" => group, "key" => key, "delete" => true} = params ->
+            ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]})
+
+          %{"group" => group, "key" => key, "value" => value} ->
+            ConfigDB.update_or_create(%{group: group, key: key, value: value})
+        end)
+        |> Enum.reject(fn {result, _} -> result == :error end)
+
+      {deleted, updated} =
+        results
+        |> Enum.map(fn {:ok, config} ->
+          Map.put(config, :db, ConfigDB.get_db_keys(config))
+        end)
+        |> Enum.split_with(fn config ->
+          Ecto.get_meta(config, :state) == :deleted
+        end)
+
+      Config.TransferTask.load_and_update_env(deleted, false)
+
+      if not Restarter.Pleroma.need_reboot?() do
+        changed_reboot_settings? =
+          (updated ++ deleted)
+          |> Enum.any?(fn config ->
+            group = ConfigDB.from_string(config.group)
+            key = ConfigDB.from_string(config.key)
+            value = ConfigDB.from_binary(config.value)
+            Config.TransferTask.pleroma_need_restart?(group, key, value)
+          end)
+
+        if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
+      end
+
+      render(conn, "index.json", %{
+        configs: updated,
+        need_reboot: Restarter.Pleroma.need_reboot?()
+      })
+    end
+  end
+
+  defp configurable_from_database do
+    if Config.get(:configurable_from_database) do
+      :ok
+    else
+      {:error, "To use this endpoint you need to enable configuration from database."}
+    end
+  end
+
+  defp whitelisted_config?(group, key) do
+    if whitelisted_configs = Config.get(:database_config_whitelist) do
+      Enum.any?(whitelisted_configs, fn
+        {whitelisted_group} ->
+          group == inspect(whitelisted_group)
+
+        {whitelisted_group, whitelisted_key} ->
+          group == inspect(whitelisted_group) && key == inspect(whitelisted_key)
+      end)
+    else
+      true
+    end
+  end
+
+  defp whitelisted_config?(%{"group" => group, "key" => key}) do
+    whitelisted_config?(group, key)
+  end
+
+  defp whitelisted_config?(%{:group => group} = config) do
+    whitelisted_config?(group, config[:key])
+  end
+end
index e493a41534bb0f8b3a0a11e0eb6ec7714e4fd803..b683a4ff37fafef52652f0facbed4740894aa4e4 100644 (file)
@@ -194,9 +194,9 @@ defmodule Pleroma.Web.Router do
     delete("/statuses/:id", StatusController, :delete)
     get("/statuses", StatusController, :index)
 
-    get("/config", AdminAPIController, :config_show)
-    post("/config", AdminAPIController, :config_update)
-    get("/config/descriptions", AdminAPIController, :config_descriptions)
+    get("/config", ConfigController, :show)
+    post("/config", ConfigController, :update)
+    get("/config/descriptions", ConfigController, :descriptions)
     get("/need_reboot", AdminAPIController, :need_reboot)
     get("/restart", AdminAPIController, :restart)
 
index ead840186beba08fca0f38276f48164401bbdcee..bd44ffed30c6c23a05d2d969927f56cdd5e2ee8c 100644 (file)
@@ -12,7 +12,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   alias Pleroma.Activity
   alias Pleroma.Config
-  alias Pleroma.ConfigDB
   alias Pleroma.HTML
   alias Pleroma.MFA
   alias Pleroma.ModerationLog
@@ -1704,1175 +1703,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "GET /api/pleroma/admin/config" do
-    setup do: clear_config(:configurable_from_database, true)
-
-    test "when configuration from database is off", %{conn: conn} do
-      Config.put(:configurable_from_database, false)
-      conn = get(conn, "/api/pleroma/admin/config")
-
-      assert json_response(conn, 400) ==
-               %{
-                 "error" => "To use this endpoint you need to enable configuration from database."
-               }
-    end
-
-    test "with settings only in db", %{conn: conn} do
-      config1 = insert(:config)
-      config2 = insert(:config)
-
-      conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})
-
-      %{
-        "configs" => [
-          %{
-            "group" => ":pleroma",
-            "key" => key1,
-            "value" => _
-          },
-          %{
-            "group" => ":pleroma",
-            "key" => key2,
-            "value" => _
-          }
-        ]
-      } = json_response(conn, 200)
-
-      assert key1 == config1.key
-      assert key2 == config2.key
-    end
-
-    test "db is added to settings that are in db", %{conn: conn} do
-      _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
-
-      %{"configs" => configs} =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      [instance_config] =
-        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
-          group == ":pleroma" and key == ":instance"
-        end)
-
-      assert instance_config["db"] == [":name"]
-    end
-
-    test "merged default setting with db settings", %{conn: conn} do
-      config1 = insert(:config)
-      config2 = insert(:config)
-
-      config3 =
-        insert(:config,
-          value: ConfigDB.to_binary(k1: :v1, k2: :v2)
-        )
-
-      %{"configs" => configs} =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert length(configs) > 3
-
-      received_configs =
-        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
-          group == ":pleroma" and key in [config1.key, config2.key, config3.key]
-        end)
-
-      assert length(received_configs) == 3
-
-      db_keys =
-        config3.value
-        |> ConfigDB.from_binary()
-        |> Keyword.keys()
-        |> ConfigDB.convert()
-
-      Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
-        assert db in [[config1.key], [config2.key], db_keys]
-
-        assert value in [
-                 ConfigDB.from_binary_with_convert(config1.value),
-                 ConfigDB.from_binary_with_convert(config2.value),
-                 ConfigDB.from_binary_with_convert(config3.value)
-               ]
-      end)
-    end
-
-    test "subkeys with full update right merge", %{conn: conn} do
-      config1 =
-        insert(:config,
-          key: ":emoji",
-          value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
-        )
-
-      config2 =
-        insert(:config,
-          key: ":assets",
-          value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
-        )
-
-      %{"configs" => configs} =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      vals =
-        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
-          group == ":pleroma" and key in [config1.key, config2.key]
-        end)
-
-      emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
-      assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
-
-      emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
-      assets_val = ConfigDB.transform_with_out_binary(assets["value"])
-
-      assert emoji_val[:groups] == [a: 1, b: 2]
-      assert assets_val[:mascots] == [a: 1, b: 2]
-    end
-  end
-
-  test "POST /api/pleroma/admin/config error", %{conn: conn} do
-    conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []})
-
-    assert json_response(conn, 400) ==
-             %{"error" => "To use this endpoint you need to enable configuration from database."}
-  end
-
-  describe "POST /api/pleroma/admin/config" do
-    setup do
-      http = Application.get_env(:pleroma, :http)
-
-      on_exit(fn ->
-        Application.delete_env(:pleroma, :key1)
-        Application.delete_env(:pleroma, :key2)
-        Application.delete_env(:pleroma, :key3)
-        Application.delete_env(:pleroma, :key4)
-        Application.delete_env(:pleroma, :keyaa1)
-        Application.delete_env(:pleroma, :keyaa2)
-        Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
-        Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
-        Application.put_env(:pleroma, :http, http)
-        Application.put_env(:tesla, :adapter, Tesla.Mock)
-        Restarter.Pleroma.refresh()
-      end)
-    end
-
-    setup do: clear_config(:configurable_from_database, true)
-
-    @tag capture_log: true
-    test "create new config setting in db", %{conn: conn} do
-      ueberauth = Application.get_env(:ueberauth, Ueberauth)
-      on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: ":pleroma", key: ":key1", value: "value1"},
-            %{
-              group: ":ueberauth",
-              key: "Ueberauth",
-              value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
-            },
-            %{
-              group: ":pleroma",
-              key: ":key2",
-              value: %{
-                ":nested_1" => "nested_value1",
-                ":nested_2" => [
-                  %{":nested_22" => "nested_value222"},
-                  %{":nested_33" => %{":nested_44" => "nested_444"}}
-                ]
-              }
-            },
-            %{
-              group: ":pleroma",
-              key: ":key3",
-              value: [
-                %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
-                %{"nested_4" => true}
-              ]
-            },
-            %{
-              group: ":pleroma",
-              key: ":key4",
-              value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
-            },
-            %{
-              group: ":idna",
-              key: ":key5",
-              value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => "value1",
-                   "db" => [":key1"]
-                 },
-                 %{
-                   "group" => ":ueberauth",
-                   "key" => "Ueberauth",
-                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
-                   "db" => [":consumer_secret"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key2",
-                   "value" => %{
-                     ":nested_1" => "nested_value1",
-                     ":nested_2" => [
-                       %{":nested_22" => "nested_value222"},
-                       %{":nested_33" => %{":nested_44" => "nested_444"}}
-                     ]
-                   },
-                   "db" => [":key2"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key3",
-                   "value" => [
-                     %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
-                     %{"nested_4" => true}
-                   ],
-                   "db" => [":key3"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key4",
-                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
-                   "db" => [":key4"]
-                 },
-                 %{
-                   "group" => ":idna",
-                   "key" => ":key5",
-                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
-                   "db" => [":key5"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :key1) == "value1"
-
-      assert Application.get_env(:pleroma, :key2) == %{
-               nested_1: "nested_value1",
-               nested_2: [
-                 %{nested_22: "nested_value222"},
-                 %{nested_33: %{nested_44: "nested_444"}}
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :key3) == [
-               %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
-               %{"nested_4" => true}
-             ]
-
-      assert Application.get_env(:pleroma, :key4) == %{
-               "endpoint" => "https://example.com",
-               nested_5: :upload
-             }
-
-      assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
-    end
-
-    test "save configs setting without explicit key", %{conn: conn} do
-      level = Application.get_env(:quack, :level)
-      meta = Application.get_env(:quack, :meta)
-      webhook_url = Application.get_env(:quack, :webhook_url)
-
-      on_exit(fn ->
-        Application.put_env(:quack, :level, level)
-        Application.put_env(:quack, :meta, meta)
-        Application.put_env(:quack, :webhook_url, webhook_url)
-      end)
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":quack",
-              key: ":level",
-              value: ":info"
-            },
-            %{
-              group: ":quack",
-              key: ":meta",
-              value: [":none"]
-            },
-            %{
-              group: ":quack",
-              key: ":webhook_url",
-              value: "https://hooks.slack.com/services/KEY"
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":quack",
-                   "key" => ":level",
-                   "value" => ":info",
-                   "db" => [":level"]
-                 },
-                 %{
-                   "group" => ":quack",
-                   "key" => ":meta",
-                   "value" => [":none"],
-                   "db" => [":meta"]
-                 },
-                 %{
-                   "group" => ":quack",
-                   "key" => ":webhook_url",
-                   "value" => "https://hooks.slack.com/services/KEY",
-                   "db" => [":webhook_url"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:quack, :level) == :info
-      assert Application.get_env(:quack, :meta) == [:none]
-      assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
-    end
-
-    test "saving config with partial update", %{conn: conn} do
-      config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{"tuple" => [":key1", 1]},
-                     %{"tuple" => [":key2", 2]},
-                     %{"tuple" => [":key3", 3]}
-                   ],
-                   "db" => [":key1", ":key2", ":key3"]
-                 }
-               ]
-             }
-    end
-
-    test "saving config which need pleroma reboot", %{conn: conn} do
-      chat = Config.get(:chat)
-      on_exit(fn -> Config.put(:chat, chat) end)
-
-      assert post(
-               conn,
-               "/api/pleroma/admin/config",
-               %{
-                 configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
-                 ]
-               }
-             )
-             |> json_response(200) == %{
-               "configs" => [
-                 %{
-                   "db" => [":enabled"],
-                   "group" => ":pleroma",
-                   "key" => ":chat",
-                   "value" => [%{"tuple" => [":enabled", true]}]
-                 }
-               ],
-               "need_reboot" => true
-             }
-
-      configs =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert configs["need_reboot"]
-
-      capture_log(fn ->
-        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
-      end) =~ "pleroma restarted"
-
-      configs =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert configs["need_reboot"] == false
-    end
-
-    test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
-      chat = Config.get(:chat)
-      on_exit(fn -> Config.put(:chat, chat) end)
-
-      assert post(
-               conn,
-               "/api/pleroma/admin/config",
-               %{
-                 configs: [
-                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
-                 ]
-               }
-             )
-             |> json_response(200) == %{
-               "configs" => [
-                 %{
-                   "db" => [":enabled"],
-                   "group" => ":pleroma",
-                   "key" => ":chat",
-                   "value" => [%{"tuple" => [":enabled", true]}]
-                 }
-               ],
-               "need_reboot" => true
-             }
-
-      assert post(conn, "/api/pleroma/admin/config", %{
-               configs: [
-                 %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
-               ]
-             })
-             |> json_response(200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{"tuple" => [":key3", 3]}
-                   ],
-                   "db" => [":key3"]
-                 }
-               ],
-               "need_reboot" => true
-             }
-
-      capture_log(fn ->
-        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
-      end) =~ "pleroma restarted"
-
-      configs =
-        conn
-        |> get("/api/pleroma/admin/config")
-        |> json_response(200)
-
-      assert configs["need_reboot"] == false
-    end
-
-    test "saving config with nested merge", %{conn: conn} do
-      config =
-        insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: config.group,
-              key: config.key,
-              value: [
-                %{"tuple" => [":key3", 3]},
-                %{
-                  "tuple" => [
-                    ":key2",
-                    [
-                      %{"tuple" => [":k2", 1]},
-                      %{"tuple" => [":k3", 3]}
-                    ]
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{"tuple" => [":key1", 1]},
-                     %{"tuple" => [":key3", 3]},
-                     %{
-                       "tuple" => [
-                         ":key2",
-                         [
-                           %{"tuple" => [":k1", 1]},
-                           %{"tuple" => [":k2", 1]},
-                           %{"tuple" => [":k3", 3]}
-                         ]
-                       ]
-                     }
-                   ],
-                   "db" => [":key1", ":key3", ":key2"]
-                 }
-               ]
-             }
-    end
-
-    test "saving special atoms", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          "configs" => [
-            %{
-              "group" => ":pleroma",
-              "key" => ":key1",
-              "value" => [
-                %{
-                  "tuple" => [
-                    ":ssl_options",
-                    [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":key1",
-                   "value" => [
-                     %{
-                       "tuple" => [
-                         ":ssl_options",
-                         [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
-                       ]
-                     }
-                   ],
-                   "db" => [":ssl_options"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :key1) == [
-               ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
-             ]
-    end
-
-    test "saving full setting if value is in full_key_update list", %{conn: conn} do
-      backends = Application.get_env(:logger, :backends)
-      on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
-
-      config =
-        insert(:config,
-          group: ":logger",
-          key: ":backends",
-          value: :erlang.term_to_binary([])
-        )
-
-      Pleroma.Config.TransferTask.load_and_update_env([], false)
-
-      assert Application.get_env(:logger, :backends) == []
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: config.group,
-              key: config.key,
-              value: [":console"]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":logger",
-                   "key" => ":backends",
-                   "value" => [
-                     ":console"
-                   ],
-                   "db" => [":backends"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:logger, :backends) == [
-               :console
-             ]
-    end
-
-    test "saving full setting if value is not keyword", %{conn: conn} do
-      config =
-        insert(:config,
-          group: ":tesla",
-          key: ":adapter",
-          value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
-        )
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":tesla",
-                   "key" => ":adapter",
-                   "value" => "Tesla.Adapter.Httpc",
-                   "db" => [":adapter"]
-                 }
-               ]
-             }
-    end
-
-    test "update config setting & delete with fallback to default value", %{
-      conn: conn,
-      admin: admin,
-      token: token
-    } do
-      ueberauth = Application.get_env(:ueberauth, Ueberauth)
-      config1 = insert(:config, key: ":keyaa1")
-      config2 = insert(:config, key: ":keyaa2")
-
-      config3 =
-        insert(:config,
-          group: ":ueberauth",
-          key: "Ueberauth"
-        )
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config1.group, key: config1.key, value: "another_value"},
-            %{group: config2.group, key: config2.key, value: "another_value"}
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => config1.key,
-                   "value" => "another_value",
-                   "db" => [":keyaa1"]
-                 },
-                 %{
-                   "group" => ":pleroma",
-                   "key" => config2.key,
-                   "value" => "another_value",
-                   "db" => [":keyaa2"]
-                 }
-               ]
-             }
-
-      assert Application.get_env(:pleroma, :keyaa1) == "another_value"
-      assert Application.get_env(:pleroma, :keyaa2) == "another_value"
-      assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
-
-      conn =
-        build_conn()
-        |> assign(:user, admin)
-        |> assign(:token, token)
-        |> post("/api/pleroma/admin/config", %{
-          configs: [
-            %{group: config2.group, key: config2.key, delete: true},
-            %{
-              group: ":ueberauth",
-              key: "Ueberauth",
-              delete: true
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => []
-             }
-
-      assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
-      refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
-    end
-
-    test "common config example", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => "Pleroma.Captcha.NotReal",
-              "value" => [
-                %{"tuple" => [":enabled", false]},
-                %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
-                %{"tuple" => [":seconds_valid", 60]},
-                %{"tuple" => [":path", ""]},
-                %{"tuple" => [":key1", nil]},
-                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
-                %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
-                %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
-                %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
-                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
-                %{"tuple" => [":name", "Pleroma"]}
-              ]
-            }
-          ]
-        })
-
-      assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => "Pleroma.Captcha.NotReal",
-                   "value" => [
-                     %{"tuple" => [":enabled", false]},
-                     %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
-                     %{"tuple" => [":seconds_valid", 60]},
-                     %{"tuple" => [":path", ""]},
-                     %{"tuple" => [":key1", nil]},
-                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
-                     %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
-                     %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
-                     %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
-                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
-                     %{"tuple" => [":name", "Pleroma"]}
-                   ],
-                   "db" => [
-                     ":enabled",
-                     ":method",
-                     ":seconds_valid",
-                     ":path",
-                     ":key1",
-                     ":partial_chain",
-                     ":regex1",
-                     ":regex2",
-                     ":regex3",
-                     ":regex4",
-                     ":name"
-                   ]
-                 }
-               ]
-             }
-    end
-
-    test "tuples with more than two values", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => "Pleroma.Web.Endpoint.NotReal",
-              "value" => [
-                %{
-                  "tuple" => [
-                    ":http",
-                    [
-                      %{
-                        "tuple" => [
-                          ":key2",
-                          [
-                            %{
-                              "tuple" => [
-                                ":_",
-                                [
-                                  %{
-                                    "tuple" => [
-                                      "/api/v1/streaming",
-                                      "Pleroma.Web.MastodonAPI.WebsocketHandler",
-                                      []
-                                    ]
-                                  },
-                                  %{
-                                    "tuple" => [
-                                      "/websocket",
-                                      "Phoenix.Endpoint.CowboyWebSocket",
-                                      %{
-                                        "tuple" => [
-                                          "Phoenix.Transports.WebSocket",
-                                          %{
-                                            "tuple" => [
-                                              "Pleroma.Web.Endpoint",
-                                              "Pleroma.Web.UserSocket",
-                                              []
-                                            ]
-                                          }
-                                        ]
-                                      }
-                                    ]
-                                  },
-                                  %{
-                                    "tuple" => [
-                                      ":_",
-                                      "Phoenix.Endpoint.Cowboy2Handler",
-                                      %{"tuple" => ["Pleroma.Web.Endpoint", []]}
-                                    ]
-                                  }
-                                ]
-                              ]
-                            }
-                          ]
-                        ]
-                      }
-                    ]
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => "Pleroma.Web.Endpoint.NotReal",
-                   "value" => [
-                     %{
-                       "tuple" => [
-                         ":http",
-                         [
-                           %{
-                             "tuple" => [
-                               ":key2",
-                               [
-                                 %{
-                                   "tuple" => [
-                                     ":_",
-                                     [
-                                       %{
-                                         "tuple" => [
-                                           "/api/v1/streaming",
-                                           "Pleroma.Web.MastodonAPI.WebsocketHandler",
-                                           []
-                                         ]
-                                       },
-                                       %{
-                                         "tuple" => [
-                                           "/websocket",
-                                           "Phoenix.Endpoint.CowboyWebSocket",
-                                           %{
-                                             "tuple" => [
-                                               "Phoenix.Transports.WebSocket",
-                                               %{
-                                                 "tuple" => [
-                                                   "Pleroma.Web.Endpoint",
-                                                   "Pleroma.Web.UserSocket",
-                                                   []
-                                                 ]
-                                               }
-                                             ]
-                                           }
-                                         ]
-                                       },
-                                       %{
-                                         "tuple" => [
-                                           ":_",
-                                           "Phoenix.Endpoint.Cowboy2Handler",
-                                           %{"tuple" => ["Pleroma.Web.Endpoint", []]}
-                                         ]
-                                       }
-                                     ]
-                                   ]
-                                 }
-                               ]
-                             ]
-                           }
-                         ]
-                       ]
-                     }
-                   ],
-                   "db" => [":http"]
-                 }
-               ]
-             }
-    end
-
-    test "settings with nesting map", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => ":key1",
-              "value" => [
-                %{"tuple" => [":key2", "some_val"]},
-                %{
-                  "tuple" => [
-                    ":key3",
-                    %{
-                      ":max_options" => 20,
-                      ":max_option_chars" => 200,
-                      ":min_expiration" => 0,
-                      ":max_expiration" => 31_536_000,
-                      "nested" => %{
-                        ":max_options" => 20,
-                        ":max_option_chars" => 200,
-                        ":min_expiration" => 0,
-                        ":max_expiration" => 31_536_000
-                      }
-                    }
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) ==
-               %{
-                 "configs" => [
-                   %{
-                     "group" => ":pleroma",
-                     "key" => ":key1",
-                     "value" => [
-                       %{"tuple" => [":key2", "some_val"]},
-                       %{
-                         "tuple" => [
-                           ":key3",
-                           %{
-                             ":max_expiration" => 31_536_000,
-                             ":max_option_chars" => 200,
-                             ":max_options" => 20,
-                             ":min_expiration" => 0,
-                             "nested" => %{
-                               ":max_expiration" => 31_536_000,
-                               ":max_option_chars" => 200,
-                               ":max_options" => 20,
-                               ":min_expiration" => 0
-                             }
-                           }
-                         ]
-                       }
-                     ],
-                     "db" => [":key2", ":key3"]
-                   }
-                 ]
-               }
-    end
-
-    test "value as map", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":pleroma",
-              "key" => ":key1",
-              "value" => %{"key" => "some_val"}
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) ==
-               %{
-                 "configs" => [
-                   %{
-                     "group" => ":pleroma",
-                     "key" => ":key1",
-                     "value" => %{"key" => "some_val"},
-                     "db" => [":key1"]
-                   }
-                 ]
-               }
-    end
-
-    test "queues key as atom", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => ":oban",
-              "key" => ":queues",
-              "value" => [
-                %{"tuple" => [":federator_incoming", 50]},
-                %{"tuple" => [":federator_outgoing", 50]},
-                %{"tuple" => [":web_push", 50]},
-                %{"tuple" => [":mailer", 10]},
-                %{"tuple" => [":transmogrifier", 20]},
-                %{"tuple" => [":scheduled_activities", 10]},
-                %{"tuple" => [":background", 5]}
-              ]
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":oban",
-                   "key" => ":queues",
-                   "value" => [
-                     %{"tuple" => [":federator_incoming", 50]},
-                     %{"tuple" => [":federator_outgoing", 50]},
-                     %{"tuple" => [":web_push", 50]},
-                     %{"tuple" => [":mailer", 10]},
-                     %{"tuple" => [":transmogrifier", 20]},
-                     %{"tuple" => [":scheduled_activities", 10]},
-                     %{"tuple" => [":background", 5]}
-                   ],
-                   "db" => [
-                     ":federator_incoming",
-                     ":federator_outgoing",
-                     ":web_push",
-                     ":mailer",
-                     ":transmogrifier",
-                     ":scheduled_activities",
-                     ":background"
-                   ]
-                 }
-               ]
-             }
-    end
-
-    test "delete part of settings by atom subkeys", %{conn: conn} do
-      config =
-        insert(:config,
-          key: ":keyaa1",
-          value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
-        )
-
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: config.group,
-              key: config.key,
-              subkeys: [":subkey1", ":subkey3"],
-              delete: true
-            }
-          ]
-        })
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":keyaa1",
-                   "value" => [%{"tuple" => [":subkey2", "val2"]}],
-                   "db" => [":subkey2"]
-                 }
-               ]
-             }
-    end
-
-    test "proxy tuple localhost", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":pleroma",
-              key: ":http",
-              value: [
-                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
-              ]
-            }
-          ]
-        })
-
-      assert %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":http",
-                   "value" => value,
-                   "db" => db
-                 }
-               ]
-             } = json_response(conn, 200)
-
-      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
-      assert ":proxy_url" in db
-    end
-
-    test "proxy tuple domain", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":pleroma",
-              key: ":http",
-              value: [
-                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
-              ]
-            }
-          ]
-        })
-
-      assert %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":http",
-                   "value" => value,
-                   "db" => db
-                 }
-               ]
-             } = json_response(conn, 200)
-
-      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
-      assert ":proxy_url" in db
-    end
-
-    test "proxy tuple ip", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              group: ":pleroma",
-              key: ":http",
-              value: [
-                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
-              ]
-            }
-          ]
-        })
-
-      assert %{
-               "configs" => [
-                 %{
-                   "group" => ":pleroma",
-                   "key" => ":http",
-                   "value" => value,
-                   "db" => db
-                 }
-               ]
-             } = json_response(conn, 200)
-
-      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
-      assert ":proxy_url" in db
-    end
-
-    @tag capture_log: true
-    test "doesn't set keys not in the whitelist", %{conn: conn} do
-      clear_config(:database_config_whitelist, [
-        {:pleroma, :key1},
-        {:pleroma, :key2},
-        {:pleroma, Pleroma.Captcha.NotReal},
-        {:not_real}
-      ])
-
-      post(conn, "/api/pleroma/admin/config", %{
-        configs: [
-          %{group: ":pleroma", key: ":key1", value: "value1"},
-          %{group: ":pleroma", key: ":key2", value: "value2"},
-          %{group: ":pleroma", key: ":key3", value: "value3"},
-          %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
-          %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
-          %{group: ":not_real", key: ":anything", value: "value6"}
-        ]
-      })
-
-      assert Application.get_env(:pleroma, :key1) == "value1"
-      assert Application.get_env(:pleroma, :key2) == "value2"
-      assert Application.get_env(:pleroma, :key3) == nil
-      assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
-      assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
-      assert Application.get_env(:not_real, :anything) == "value6"
-    end
-  end
-
   describe "GET /api/pleroma/admin/restart" do
     setup do: clear_config(:configurable_from_database, true)
 
@@ -3481,56 +2311,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "GET /api/pleroma/admin/config/descriptions" do
-    test "structure", %{conn: conn} do
-      admin = insert(:user, is_admin: true)
-
-      conn =
-        assign(conn, :user, admin)
-        |> get("/api/pleroma/admin/config/descriptions")
-
-      assert [child | _others] = json_response(conn, 200)
-
-      assert child["children"]
-      assert child["key"]
-      assert String.starts_with?(child["group"], ":")
-      assert child["description"]
-    end
-
-    test "filters by database configuration whitelist", %{conn: conn} do
-      clear_config(:database_config_whitelist, [
-        {:pleroma, :instance},
-        {:pleroma, :activitypub},
-        {:pleroma, Pleroma.Upload},
-        {:esshd}
-      ])
-
-      admin = insert(:user, is_admin: true)
-
-      conn =
-        assign(conn, :user, admin)
-        |> get("/api/pleroma/admin/config/descriptions")
-
-      children = json_response(conn, 200)
-
-      assert length(children) == 4
-
-      assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
-
-      instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
-      assert instance["children"]
-
-      activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
-      assert activitypub["children"]
-
-      web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
-      assert web_endpoint["children"]
-
-      esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
-      assert esshd["children"]
-    end
-  end
-
   describe "/api/pleroma/admin/stats" do
     test "status visibility count", %{conn: conn} do
       admin = insert(:user, is_admin: true)
diff --git a/test/web/admin_api/controllers/config_controller_test.exs b/test/web/admin_api/controllers/config_controller_test.exs
new file mode 100644 (file)
index 0000000..9bc6fd9
--- /dev/null
@@ -0,0 +1,1244 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  import ExUnit.CaptureLog
+  import Pleroma.Factory
+
+  alias Pleroma.Config
+  alias Pleroma.ConfigDB
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "GET /api/pleroma/admin/config" do
+    setup do: clear_config(:configurable_from_database, true)
+
+    test "when configuration from database is off", %{conn: conn} do
+      Config.put(:configurable_from_database, false)
+      conn = get(conn, "/api/pleroma/admin/config")
+
+      assert json_response(conn, 400) ==
+               %{
+                 "error" => "To use this endpoint you need to enable configuration from database."
+               }
+    end
+
+    test "with settings only in db", %{conn: conn} do
+      config1 = insert(:config)
+      config2 = insert(:config)
+
+      conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})
+
+      %{
+        "configs" => [
+          %{
+            "group" => ":pleroma",
+            "key" => key1,
+            "value" => _
+          },
+          %{
+            "group" => ":pleroma",
+            "key" => key2,
+            "value" => _
+          }
+        ]
+      } = json_response(conn, 200)
+
+      assert key1 == config1.key
+      assert key2 == config2.key
+    end
+
+    test "db is added to settings that are in db", %{conn: conn} do
+      _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      [instance_config] =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key == ":instance"
+        end)
+
+      assert instance_config["db"] == [":name"]
+    end
+
+    test "merged default setting with db settings", %{conn: conn} do
+      config1 = insert(:config)
+      config2 = insert(:config)
+
+      config3 =
+        insert(:config,
+          value: ConfigDB.to_binary(k1: :v1, k2: :v2)
+        )
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      assert length(configs) > 3
+
+      received_configs =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key in [config1.key, config2.key, config3.key]
+        end)
+
+      assert length(received_configs) == 3
+
+      db_keys =
+        config3.value
+        |> ConfigDB.from_binary()
+        |> Keyword.keys()
+        |> ConfigDB.convert()
+
+      Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
+        assert db in [[config1.key], [config2.key], db_keys]
+
+        assert value in [
+                 ConfigDB.from_binary_with_convert(config1.value),
+                 ConfigDB.from_binary_with_convert(config2.value),
+                 ConfigDB.from_binary_with_convert(config3.value)
+               ]
+      end)
+    end
+
+    test "subkeys with full update right merge", %{conn: conn} do
+      config1 =
+        insert(:config,
+          key: ":emoji",
+          value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
+        )
+
+      config2 =
+        insert(:config,
+          key: ":assets",
+          value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
+        )
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      vals =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key in [config1.key, config2.key]
+        end)
+
+      emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
+      assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
+
+      emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
+      assets_val = ConfigDB.transform_with_out_binary(assets["value"])
+
+      assert emoji_val[:groups] == [a: 1, b: 2]
+      assert assets_val[:mascots] == [a: 1, b: 2]
+    end
+  end
+
+  test "POST /api/pleroma/admin/config error", %{conn: conn} do
+    conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []})
+
+    assert json_response(conn, 400) ==
+             %{"error" => "To use this endpoint you need to enable configuration from database."}
+  end
+
+  describe "POST /api/pleroma/admin/config" do
+    setup do
+      http = Application.get_env(:pleroma, :http)
+
+      on_exit(fn ->
+        Application.delete_env(:pleroma, :key1)
+        Application.delete_env(:pleroma, :key2)
+        Application.delete_env(:pleroma, :key3)
+        Application.delete_env(:pleroma, :key4)
+        Application.delete_env(:pleroma, :keyaa1)
+        Application.delete_env(:pleroma, :keyaa2)
+        Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
+        Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
+        Application.put_env(:pleroma, :http, http)
+        Application.put_env(:tesla, :adapter, Tesla.Mock)
+        Restarter.Pleroma.refresh()
+      end)
+    end
+
+    setup do: clear_config(:configurable_from_database, true)
+
+    @tag capture_log: true
+    test "create new config setting in db", %{conn: conn} do
+      ueberauth = Application.get_env(:ueberauth, Ueberauth)
+      on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{group: ":pleroma", key: ":key1", value: "value1"},
+            %{
+              group: ":ueberauth",
+              key: "Ueberauth",
+              value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
+            },
+            %{
+              group: ":pleroma",
+              key: ":key2",
+              value: %{
+                ":nested_1" => "nested_value1",
+                ":nested_2" => [
+                  %{":nested_22" => "nested_value222"},
+                  %{":nested_33" => %{":nested_44" => "nested_444"}}
+                ]
+              }
+            },
+            %{
+              group: ":pleroma",
+              key: ":key3",
+              value: [
+                %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
+                %{"nested_4" => true}
+              ]
+            },
+            %{
+              group: ":pleroma",
+              key: ":key4",
+              value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
+            },
+            %{
+              group: ":idna",
+              key: ":key5",
+              value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => "value1",
+                   "db" => [":key1"]
+                 },
+                 %{
+                   "group" => ":ueberauth",
+                   "key" => "Ueberauth",
+                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
+                   "db" => [":consumer_secret"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key2",
+                   "value" => %{
+                     ":nested_1" => "nested_value1",
+                     ":nested_2" => [
+                       %{":nested_22" => "nested_value222"},
+                       %{":nested_33" => %{":nested_44" => "nested_444"}}
+                     ]
+                   },
+                   "db" => [":key2"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key3",
+                   "value" => [
+                     %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
+                     %{"nested_4" => true}
+                   ],
+                   "db" => [":key3"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key4",
+                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
+                   "db" => [":key4"]
+                 },
+                 %{
+                   "group" => ":idna",
+                   "key" => ":key5",
+                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
+                   "db" => [":key5"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :key1) == "value1"
+
+      assert Application.get_env(:pleroma, :key2) == %{
+               nested_1: "nested_value1",
+               nested_2: [
+                 %{nested_22: "nested_value222"},
+                 %{nested_33: %{nested_44: "nested_444"}}
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :key3) == [
+               %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
+               %{"nested_4" => true}
+             ]
+
+      assert Application.get_env(:pleroma, :key4) == %{
+               "endpoint" => "https://example.com",
+               nested_5: :upload
+             }
+
+      assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
+    end
+
+    test "save configs setting without explicit key", %{conn: conn} do
+      level = Application.get_env(:quack, :level)
+      meta = Application.get_env(:quack, :meta)
+      webhook_url = Application.get_env(:quack, :webhook_url)
+
+      on_exit(fn ->
+        Application.put_env(:quack, :level, level)
+        Application.put_env(:quack, :meta, meta)
+        Application.put_env(:quack, :webhook_url, webhook_url)
+      end)
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":quack",
+              key: ":level",
+              value: ":info"
+            },
+            %{
+              group: ":quack",
+              key: ":meta",
+              value: [":none"]
+            },
+            %{
+              group: ":quack",
+              key: ":webhook_url",
+              value: "https://hooks.slack.com/services/KEY"
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":quack",
+                   "key" => ":level",
+                   "value" => ":info",
+                   "db" => [":level"]
+                 },
+                 %{
+                   "group" => ":quack",
+                   "key" => ":meta",
+                   "value" => [":none"],
+                   "db" => [":meta"]
+                 },
+                 %{
+                   "group" => ":quack",
+                   "key" => ":webhook_url",
+                   "value" => "https://hooks.slack.com/services/KEY",
+                   "db" => [":webhook_url"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:quack, :level) == :info
+      assert Application.get_env(:quack, :meta) == [:none]
+      assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
+    end
+
+    test "saving config with partial update", %{conn: conn} do
+      config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key1", 1]},
+                     %{"tuple" => [":key2", 2]},
+                     %{"tuple" => [":key3", 3]}
+                   ],
+                   "db" => [":key1", ":key2", ":key3"]
+                 }
+               ]
+             }
+    end
+
+    test "saving config which need pleroma reboot", %{conn: conn} do
+      chat = Config.get(:chat)
+      on_exit(fn -> Config.put(:chat, chat) end)
+
+      assert post(
+               conn,
+               "/api/pleroma/admin/config",
+               %{
+                 configs: [
+                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                 ]
+               }
+             )
+             |> json_response(200) == %{
+               "configs" => [
+                 %{
+                   "db" => [":enabled"],
+                   "group" => ":pleroma",
+                   "key" => ":chat",
+                   "value" => [%{"tuple" => [":enabled", true]}]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      assert configs["need_reboot"]
+
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+      end) =~ "pleroma restarted"
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      assert configs["need_reboot"] == false
+    end
+
+    test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
+      chat = Config.get(:chat)
+      on_exit(fn -> Config.put(:chat, chat) end)
+
+      assert post(
+               conn,
+               "/api/pleroma/admin/config",
+               %{
+                 configs: [
+                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                 ]
+               }
+             )
+             |> json_response(200) == %{
+               "configs" => [
+                 %{
+                   "db" => [":enabled"],
+                   "group" => ":pleroma",
+                   "key" => ":chat",
+                   "value" => [%{"tuple" => [":enabled", true]}]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      assert post(conn, "/api/pleroma/admin/config", %{
+               configs: [
+                 %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
+               ]
+             })
+             |> json_response(200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key3", 3]}
+                   ],
+                   "db" => [":key3"]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+      end) =~ "pleroma restarted"
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      assert configs["need_reboot"] == false
+    end
+
+    test "saving config with nested merge", %{conn: conn} do
+      config =
+        insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              value: [
+                %{"tuple" => [":key3", 3]},
+                %{
+                  "tuple" => [
+                    ":key2",
+                    [
+                      %{"tuple" => [":k2", 1]},
+                      %{"tuple" => [":k3", 3]}
+                    ]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key1", 1]},
+                     %{"tuple" => [":key3", 3]},
+                     %{
+                       "tuple" => [
+                         ":key2",
+                         [
+                           %{"tuple" => [":k1", 1]},
+                           %{"tuple" => [":k2", 1]},
+                           %{"tuple" => [":k3", 3]}
+                         ]
+                       ]
+                     }
+                   ],
+                   "db" => [":key1", ":key3", ":key2"]
+                 }
+               ]
+             }
+    end
+
+    test "saving special atoms", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          "configs" => [
+            %{
+              "group" => ":pleroma",
+              "key" => ":key1",
+              "value" => [
+                %{
+                  "tuple" => [
+                    ":ssl_options",
+                    [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{
+                       "tuple" => [
+                         ":ssl_options",
+                         [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+                       ]
+                     }
+                   ],
+                   "db" => [":ssl_options"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :key1) == [
+               ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
+             ]
+    end
+
+    test "saving full setting if value is in full_key_update list", %{conn: conn} do
+      backends = Application.get_env(:logger, :backends)
+      on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
+
+      config =
+        insert(:config,
+          group: ":logger",
+          key: ":backends",
+          value: :erlang.term_to_binary([])
+        )
+
+      Pleroma.Config.TransferTask.load_and_update_env([], false)
+
+      assert Application.get_env(:logger, :backends) == []
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              value: [":console"]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":logger",
+                   "key" => ":backends",
+                   "value" => [
+                     ":console"
+                   ],
+                   "db" => [":backends"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:logger, :backends) == [
+               :console
+             ]
+    end
+
+    test "saving full setting if value is not keyword", %{conn: conn} do
+      config =
+        insert(:config,
+          group: ":tesla",
+          key: ":adapter",
+          value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
+        )
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":tesla",
+                   "key" => ":adapter",
+                   "value" => "Tesla.Adapter.Httpc",
+                   "db" => [":adapter"]
+                 }
+               ]
+             }
+    end
+
+    test "update config setting & delete with fallback to default value", %{
+      conn: conn,
+      admin: admin,
+      token: token
+    } do
+      ueberauth = Application.get_env(:ueberauth, Ueberauth)
+      config1 = insert(:config, key: ":keyaa1")
+      config2 = insert(:config, key: ":keyaa2")
+
+      config3 =
+        insert(:config,
+          group: ":ueberauth",
+          key: "Ueberauth"
+        )
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config1.group, key: config1.key, value: "another_value"},
+            %{group: config2.group, key: config2.key, value: "another_value"}
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => config1.key,
+                   "value" => "another_value",
+                   "db" => [":keyaa1"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => config2.key,
+                   "value" => "another_value",
+                   "db" => [":keyaa2"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :keyaa1) == "another_value"
+      assert Application.get_env(:pleroma, :keyaa2) == "another_value"
+      assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> assign(:token, token)
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config2.group, key: config2.key, delete: true},
+            %{
+              group: ":ueberauth",
+              key: "Ueberauth",
+              delete: true
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => []
+             }
+
+      assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
+      refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
+    end
+
+    test "common config example", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => "Pleroma.Captcha.NotReal",
+              "value" => [
+                %{"tuple" => [":enabled", false]},
+                %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+                %{"tuple" => [":seconds_valid", 60]},
+                %{"tuple" => [":path", ""]},
+                %{"tuple" => [":key1", nil]},
+                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+                %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
+                %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
+                %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
+                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
+                %{"tuple" => [":name", "Pleroma"]}
+              ]
+            }
+          ]
+        })
+
+      assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => "Pleroma.Captcha.NotReal",
+                   "value" => [
+                     %{"tuple" => [":enabled", false]},
+                     %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+                     %{"tuple" => [":seconds_valid", 60]},
+                     %{"tuple" => [":path", ""]},
+                     %{"tuple" => [":key1", nil]},
+                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+                     %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
+                     %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
+                     %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
+                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
+                     %{"tuple" => [":name", "Pleroma"]}
+                   ],
+                   "db" => [
+                     ":enabled",
+                     ":method",
+                     ":seconds_valid",
+                     ":path",
+                     ":key1",
+                     ":partial_chain",
+                     ":regex1",
+                     ":regex2",
+                     ":regex3",
+                     ":regex4",
+                     ":name"
+                   ]
+                 }
+               ]
+             }
+    end
+
+    test "tuples with more than two values", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => "Pleroma.Web.Endpoint.NotReal",
+              "value" => [
+                %{
+                  "tuple" => [
+                    ":http",
+                    [
+                      %{
+                        "tuple" => [
+                          ":key2",
+                          [
+                            %{
+                              "tuple" => [
+                                ":_",
+                                [
+                                  %{
+                                    "tuple" => [
+                                      "/api/v1/streaming",
+                                      "Pleroma.Web.MastodonAPI.WebsocketHandler",
+                                      []
+                                    ]
+                                  },
+                                  %{
+                                    "tuple" => [
+                                      "/websocket",
+                                      "Phoenix.Endpoint.CowboyWebSocket",
+                                      %{
+                                        "tuple" => [
+                                          "Phoenix.Transports.WebSocket",
+                                          %{
+                                            "tuple" => [
+                                              "Pleroma.Web.Endpoint",
+                                              "Pleroma.Web.UserSocket",
+                                              []
+                                            ]
+                                          }
+                                        ]
+                                      }
+                                    ]
+                                  },
+                                  %{
+                                    "tuple" => [
+                                      ":_",
+                                      "Phoenix.Endpoint.Cowboy2Handler",
+                                      %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+                                    ]
+                                  }
+                                ]
+                              ]
+                            }
+                          ]
+                        ]
+                      }
+                    ]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => "Pleroma.Web.Endpoint.NotReal",
+                   "value" => [
+                     %{
+                       "tuple" => [
+                         ":http",
+                         [
+                           %{
+                             "tuple" => [
+                               ":key2",
+                               [
+                                 %{
+                                   "tuple" => [
+                                     ":_",
+                                     [
+                                       %{
+                                         "tuple" => [
+                                           "/api/v1/streaming",
+                                           "Pleroma.Web.MastodonAPI.WebsocketHandler",
+                                           []
+                                         ]
+                                       },
+                                       %{
+                                         "tuple" => [
+                                           "/websocket",
+                                           "Phoenix.Endpoint.CowboyWebSocket",
+                                           %{
+                                             "tuple" => [
+                                               "Phoenix.Transports.WebSocket",
+                                               %{
+                                                 "tuple" => [
+                                                   "Pleroma.Web.Endpoint",
+                                                   "Pleroma.Web.UserSocket",
+                                                   []
+                                                 ]
+                                               }
+                                             ]
+                                           }
+                                         ]
+                                       },
+                                       %{
+                                         "tuple" => [
+                                           ":_",
+                                           "Phoenix.Endpoint.Cowboy2Handler",
+                                           %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+                                         ]
+                                       }
+                                     ]
+                                   ]
+                                 }
+                               ]
+                             ]
+                           }
+                         ]
+                       ]
+                     }
+                   ],
+                   "db" => [":http"]
+                 }
+               ]
+             }
+    end
+
+    test "settings with nesting map", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => ":key1",
+              "value" => [
+                %{"tuple" => [":key2", "some_val"]},
+                %{
+                  "tuple" => [
+                    ":key3",
+                    %{
+                      ":max_options" => 20,
+                      ":max_option_chars" => 200,
+                      ":min_expiration" => 0,
+                      ":max_expiration" => 31_536_000,
+                      "nested" => %{
+                        ":max_options" => 20,
+                        ":max_option_chars" => 200,
+                        ":min_expiration" => 0,
+                        ":max_expiration" => 31_536_000
+                      }
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) ==
+               %{
+                 "configs" => [
+                   %{
+                     "group" => ":pleroma",
+                     "key" => ":key1",
+                     "value" => [
+                       %{"tuple" => [":key2", "some_val"]},
+                       %{
+                         "tuple" => [
+                           ":key3",
+                           %{
+                             ":max_expiration" => 31_536_000,
+                             ":max_option_chars" => 200,
+                             ":max_options" => 20,
+                             ":min_expiration" => 0,
+                             "nested" => %{
+                               ":max_expiration" => 31_536_000,
+                               ":max_option_chars" => 200,
+                               ":max_options" => 20,
+                               ":min_expiration" => 0
+                             }
+                           }
+                         ]
+                       }
+                     ],
+                     "db" => [":key2", ":key3"]
+                   }
+                 ]
+               }
+    end
+
+    test "value as map", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":pleroma",
+              "key" => ":key1",
+              "value" => %{"key" => "some_val"}
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) ==
+               %{
+                 "configs" => [
+                   %{
+                     "group" => ":pleroma",
+                     "key" => ":key1",
+                     "value" => %{"key" => "some_val"},
+                     "db" => [":key1"]
+                   }
+                 ]
+               }
+    end
+
+    test "queues key as atom", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              "group" => ":oban",
+              "key" => ":queues",
+              "value" => [
+                %{"tuple" => [":federator_incoming", 50]},
+                %{"tuple" => [":federator_outgoing", 50]},
+                %{"tuple" => [":web_push", 50]},
+                %{"tuple" => [":mailer", 10]},
+                %{"tuple" => [":transmogrifier", 20]},
+                %{"tuple" => [":scheduled_activities", 10]},
+                %{"tuple" => [":background", 5]}
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":oban",
+                   "key" => ":queues",
+                   "value" => [
+                     %{"tuple" => [":federator_incoming", 50]},
+                     %{"tuple" => [":federator_outgoing", 50]},
+                     %{"tuple" => [":web_push", 50]},
+                     %{"tuple" => [":mailer", 10]},
+                     %{"tuple" => [":transmogrifier", 20]},
+                     %{"tuple" => [":scheduled_activities", 10]},
+                     %{"tuple" => [":background", 5]}
+                   ],
+                   "db" => [
+                     ":federator_incoming",
+                     ":federator_outgoing",
+                     ":web_push",
+                     ":mailer",
+                     ":transmogrifier",
+                     ":scheduled_activities",
+                     ":background"
+                   ]
+                 }
+               ]
+             }
+    end
+
+    test "delete part of settings by atom subkeys", %{conn: conn} do
+      config =
+        insert(:config,
+          key: ":keyaa1",
+          value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
+        )
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              subkeys: [":subkey1", ":subkey3"],
+              delete: true
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":keyaa1",
+                   "value" => [%{"tuple" => [":subkey2", "val2"]}],
+                   "db" => [":subkey2"]
+                 }
+               ]
+             }
+    end
+
+    test "proxy tuple localhost", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
+              ]
+            }
+          ]
+        })
+
+      assert %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => value,
+                   "db" => db
+                 }
+               ]
+             } = json_response(conn, 200)
+
+      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
+      assert ":proxy_url" in db
+    end
+
+    test "proxy tuple domain", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
+              ]
+            }
+          ]
+        })
+
+      assert %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => value,
+                   "db" => db
+                 }
+               ]
+             } = json_response(conn, 200)
+
+      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
+      assert ":proxy_url" in db
+    end
+
+    test "proxy tuple ip", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
+              ]
+            }
+          ]
+        })
+
+      assert %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => value,
+                   "db" => db
+                 }
+               ]
+             } = json_response(conn, 200)
+
+      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
+      assert ":proxy_url" in db
+    end
+
+    @tag capture_log: true
+    test "doesn't set keys not in the whitelist", %{conn: conn} do
+      clear_config(:database_config_whitelist, [
+        {:pleroma, :key1},
+        {:pleroma, :key2},
+        {:pleroma, Pleroma.Captcha.NotReal},
+        {:not_real}
+      ])
+
+      post(conn, "/api/pleroma/admin/config", %{
+        configs: [
+          %{group: ":pleroma", key: ":key1", value: "value1"},
+          %{group: ":pleroma", key: ":key2", value: "value2"},
+          %{group: ":pleroma", key: ":key3", value: "value3"},
+          %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
+          %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
+          %{group: ":not_real", key: ":anything", value: "value6"}
+        ]
+      })
+
+      assert Application.get_env(:pleroma, :key1) == "value1"
+      assert Application.get_env(:pleroma, :key2) == "value2"
+      assert Application.get_env(:pleroma, :key3) == nil
+      assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
+      assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
+      assert Application.get_env(:not_real, :anything) == "value6"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/config/descriptions" do
+    test "structure", %{conn: conn} do
+      admin = insert(:user, is_admin: true)
+
+      conn =
+        assign(conn, :user, admin)
+        |> get("/api/pleroma/admin/config/descriptions")
+
+      assert [child | _others] = json_response(conn, 200)
+
+      assert child["children"]
+      assert child["key"]
+      assert String.starts_with?(child["group"], ":")
+      assert child["description"]
+    end
+
+    test "filters by database configuration whitelist", %{conn: conn} do
+      clear_config(:database_config_whitelist, [
+        {:pleroma, :instance},
+        {:pleroma, :activitypub},
+        {:pleroma, Pleroma.Upload},
+        {:esshd}
+      ])
+
+      admin = insert(:user, is_admin: true)
+
+      conn =
+        assign(conn, :user, admin)
+        |> get("/api/pleroma/admin/config/descriptions")
+
+      children = json_response(conn, 200)
+
+      assert length(children) == 4
+
+      assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
+
+      instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
+      assert instance["children"]
+
+      activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
+      assert activitypub["children"]
+
+      web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
+      assert web_endpoint["children"]
+
+      esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
+      assert esshd["children"]
+    end
+  end
+end