releases support
[akkoma] / lib / pleroma / config / config_db.ex
index e433ce442ebce8f88721c7678d88a1bbde6dd70b..85a049f8a00d8907d20f1ffe3d00eae2a9f0b534 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.ConfigDB do
   use Ecto.Schema
   import Ecto.Changeset
+  import Ecto.Query
   import Pleroma.Web.Gettext
   alias __MODULE__
   alias Pleroma.Repo
@@ -15,10 +16,25 @@ defmodule Pleroma.ConfigDB do
     field(:key, :string)
     field(:group, :string)
     field(:value, :binary)
+    field(:db, {:array, :string}, virtual: true, default: [])
 
     timestamps()
   end
 
+  @spec get_all_as_keyword() :: keyword()
+  def get_all_as_keyword do
+    ConfigDB
+    |> select([c], {c.group, c.key, c.value})
+    |> Repo.all()
+    |> Enum.reduce([], fn {group, key, value}, acc ->
+      group = ConfigDB.from_string(group)
+      key = ConfigDB.from_string(key)
+      value = from_binary(value)
+
+      Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
+    end)
+  end
+
   @spec get_by_params(map()) :: ConfigDB.t() | nil
   def get_by_params(params), do: Repo.get_by(ConfigDB, params)
 
@@ -46,9 +62,73 @@ defmodule Pleroma.ConfigDB do
     |> Repo.update()
   end
 
+  @spec get_db_keys(ConfigDB.t()) :: [String.t()]
+  def get_db_keys(%ConfigDB{} = config) do
+    config.value
+    |> ConfigDB.from_binary()
+    |> get_db_keys(config.key)
+  end
+
+  @spec get_db_keys(keyword(), any()) :: [String.t()]
+  def get_db_keys(value, key) do
+    if Keyword.keyword?(value) do
+      value |> Keyword.keys() |> Enum.map(&convert(&1))
+    else
+      [convert(key)]
+    end
+  end
+
+  @full_subkey_update [
+    {:pleroma, :assets, :mascots},
+    {:pleroma, :emoji, :groups},
+    {:pleroma, :workers, :retries},
+    {:pleroma, :mrf_subchain, :match_actor},
+    {:pleroma, :mrf_keyword, :replace}
+  ]
+
+  @spec deep_merge(atom(), atom(), keyword(), keyword()) :: keyword()
+  def deep_merge(group, key, old_value, new_value) do
+    old_keys =
+      old_value
+      |> Keyword.keys()
+      |> MapSet.new()
+
+    new_keys =
+      new_value
+      |> Keyword.keys()
+      |> MapSet.new()
+
+    intersect_keys = old_keys |> MapSet.intersection(new_keys) |> MapSet.to_list()
+
+    subkeys = sub_key_full_update(group, key, intersect_keys)
+
+    merged_value = DeepMerge.deep_merge(old_value, new_value)
+
+    Enum.reduce(subkeys, merged_value, fn subkey, acc ->
+      Keyword.put(acc, subkey, new_value[subkey])
+    end)
+  end
+
+  @spec sub_key_full_update?(atom(), atom(), [Keyword.key()]) :: boolean()
+  def sub_key_full_update?(group, key, subkeys) do
+    Enum.any?(@full_subkey_update, fn {g, k, subkey} ->
+      g == group and k == key and subkey in subkeys
+    end)
+  end
+
+  defp sub_key_full_update(group, key, subkeys) do
+    Enum.map(@full_subkey_update, fn
+      {g, k, subkey} when g == group and k == key ->
+        if subkey in subkeys, do: subkey, else: []
+
+      _ ->
+        []
+    end)
+    |> List.flatten()
+  end
+
   @full_key_update [
     {:pleroma, :ecto_repos},
-    {:pleroma, :assets},
     {:quack, :meta},
     {:mime, :types},
     {:cors_plug, [:max_age, :methods, :expose, :headers]},
@@ -82,8 +162,14 @@ defmodule Pleroma.ConfigDB do
          old_value <- from_binary(config.value),
          transformed_value <- do_transform(params[:value]),
          {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config},
-         new_value <- DeepMerge.deep_merge(old_value, transformed_value) do
-      ConfigDB.update(config, %{value: new_value, transformed?: true})
+         new_value <-
+           deep_merge(
+             ConfigDB.from_string(config.group),
+             ConfigDB.from_string(config.key),
+             old_value,
+             transformed_value
+           ) do
+      ConfigDB.update(config, %{value: new_value})
     else
       {reason, false, config} when reason in [:partial_update, :can_be_merged] ->
         ConfigDB.update(config, params)
@@ -137,6 +223,9 @@ defmodule Pleroma.ConfigDB do
     end
   end
 
+  @spec convert(any()) :: any()
+  def convert(entity), do: do_convert(entity)
+
   defp do_convert(entity) when is_list(entity) do
     for v <- entity, into: [], do: do_convert(v)
   end
@@ -174,7 +263,6 @@ defmodule Pleroma.ConfigDB do
     }
   end
 
-  defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
   # TODO: will become useless after removing hackney
   defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
 
@@ -202,6 +290,9 @@ defmodule Pleroma.ConfigDB do
 
   def transform(entity), do: to_binary(entity)
 
+  @spec transform_with_out_binary(any()) :: any()
+  def transform_with_out_binary(entity), do: do_transform(entity)
+
   @spec to_binary(any()) :: binary()
   def to_binary(entity), do: :erlang.term_to_binary(entity)
 
@@ -211,14 +302,13 @@ defmodule Pleroma.ConfigDB do
     {:proxy_url, {do_transform_string(type), parse_host(host), port}}
   end
 
-  defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
-    {dispatch_settings, []} = do_eval(entity)
-    {:dispatch, [dispatch_settings]}
-  end
-
   # TODO: will become useless after removing hackney
   defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
-    {partial_chain, []} = do_eval(entity)
+    {partial_chain, []} =
+      entity
+      |> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
+      |> Code.eval_string()
+
     {:partial_chain, partial_chain}
   end
 
@@ -301,12 +391,7 @@ defmodule Pleroma.ConfigDB do
 
   @spec is_module_name?(String.t()) :: boolean()
   def is_module_name?(string) do
-    Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack)\./, string) or
+    Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth)\./, string) or
       string in ["Oban", "Ueberauth", "ExSyslogger"]
   end
-
-  defp do_eval(entity) do
-    cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
-    Code.eval_string(cleaned_string)
-  end
 end