merge fix
[akkoma] / lib / pleroma / config / config_db.ex
index 6d48e030000995dd28c13a8fd4e2a5e068f787ec..0091f7a1bff2abef2af459183db07bb9ec7fed59 100644 (file)
@@ -16,6 +16,7 @@ defmodule Pleroma.ConfigDB do
     field(:key, :string)
     field(:group, :string)
     field(:value, :binary)
+    field(:db, {:array, :string}, virtual: true, default: [])
 
     timestamps()
   end
@@ -61,9 +62,91 @@ 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 = ConfigDB.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
+
+  def merge(config1, config2) when is_list(config1) and is_list(config2) do
+    Keyword.merge(config1, config2, fn _, app1, app2 ->
+      if Keyword.keyword?(app1) and Keyword.keyword?(app2) do
+        Keyword.merge(app1, app2, &deep_merge/3)
+      else
+        app2
+      end
+    end)
+  end
+
+  defp deep_merge(_key, value1, value2) do
+    if Keyword.keyword?(value1) and Keyword.keyword?(value2) do
+      Keyword.merge(value1, value2, &deep_merge/3)
+    else
+      value2
+    end
+  end
+
   @full_key_update [
     {:pleroma, :ecto_repos},
-    {:pleroma, :assets},
     {:quack, :meta},
     {:mime, :types},
     {:cors_plug, [:max_age, :methods, :expose, :headers]},
@@ -97,8 +180,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)
@@ -219,6 +308,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)
 
@@ -317,7 +409,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
 end