namespace email settings keys per adapter
All checks were successful
deploy / deploy (push) Successful in 57s
All checks were successful
deploy / deploy (push) Successful in 57s
Settings keys like api_key were shared across providers, so switching from e.g. Postmark to SendGrid showed the old API key. Now each adapter gets its own namespaced key (email_postmark_api_key, etc.) so credentials persist independently and switching back pre-fills previously saved values. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
366a1e6a48
commit
194fec8240
@ -40,10 +40,12 @@ defmodule Berrypod.Mailer do
|
|||||||
adapter_info ->
|
adapter_info ->
|
||||||
config =
|
config =
|
||||||
for field <- adapter_info.fields, into: %{} do
|
for field <- adapter_info.fields, into: %{} do
|
||||||
|
settings_key = Adapters.settings_key(adapter_info.key, field.key)
|
||||||
|
|
||||||
value =
|
value =
|
||||||
case field.type do
|
case field.type do
|
||||||
:secret -> Settings.secret_hint("email_#{field.key}")
|
:secret -> Settings.secret_hint(settings_key)
|
||||||
_ -> Settings.get_setting("email_#{field.key}")
|
_ -> Settings.get_setting(settings_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
{field.key, value}
|
{field.key, value}
|
||||||
@ -114,7 +116,7 @@ defmodule Berrypod.Mailer do
|
|||||||
opts =
|
opts =
|
||||||
for field <- adapter_info.fields, reduce: [] do
|
for field <- adapter_info.fields, reduce: [] do
|
||||||
acc ->
|
acc ->
|
||||||
settings_key = "email_#{field.key}"
|
settings_key = Adapters.settings_key(adapter_info.key, field.key)
|
||||||
|
|
||||||
value =
|
value =
|
||||||
case field.type do
|
case field.type do
|
||||||
|
|||||||
@ -59,6 +59,17 @@ defmodule Berrypod.Mailer.Adapters do
|
|||||||
%Field{key: "secret", label: "Secret key", type: :secret, required: true}
|
%Field{key: "secret", label: "Secret key", type: :secret, required: true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%Adapter{
|
||||||
|
key: "mailersend",
|
||||||
|
name: "MailerSend",
|
||||||
|
module: Swoosh.Adapters.MailerSend,
|
||||||
|
description: "Generous free tier, good analytics dashboard.",
|
||||||
|
tags: ["All email", "EU option"],
|
||||||
|
url: "https://www.mailersend.com",
|
||||||
|
fields: [
|
||||||
|
%Field{key: "api_key", label: "API key", type: :secret, required: true}
|
||||||
|
]
|
||||||
|
},
|
||||||
%Adapter{
|
%Adapter{
|
||||||
key: "resend",
|
key: "resend",
|
||||||
name: "Resend",
|
name: "Resend",
|
||||||
@ -126,15 +137,18 @@ defmodule Berrypod.Mailer.Adapters do
|
|||||||
Enum.find(@adapters, &(&1.key == key))
|
Enum.find(@adapters, &(&1.key == key))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Returns the settings keys for an adapter's fields (prefixed with `email_`)."
|
@doc "Returns the namespaced settings key for an adapter field."
|
||||||
def field_keys(%{fields: fields}) do
|
def settings_key(adapter_key, field_key) do
|
||||||
Enum.map(fields, &"email_#{&1.key}")
|
"email_#{adapter_key}_#{field_key}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Returns the settings keys for an adapter's fields."
|
||||||
|
def field_keys(%{key: adapter_key, fields: fields}) do
|
||||||
|
Enum.map(fields, &settings_key(adapter_key, &1.key))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Returns all possible settings keys across all adapters."
|
@doc "Returns all possible settings keys across all adapters."
|
||||||
def all_field_keys do
|
def all_field_keys do
|
||||||
@adapters
|
Enum.flat_map(@adapters, &field_keys/1)
|
||||||
|> Enum.flat_map(&field_keys/1)
|
|
||||||
|> Enum.uniq()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -30,6 +30,26 @@ defmodule BerrypodWeb.Admin.EmailSettings do
|
|||||||
|> assign(:form, to_form(%{}, as: :email))}
|
|> assign(:form, to_form(%{}, as: :email))}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp load_adapter_values(adapter_key) do
|
||||||
|
case Adapters.get(adapter_key) do
|
||||||
|
nil ->
|
||||||
|
%{}
|
||||||
|
|
||||||
|
adapter_info ->
|
||||||
|
for field <- adapter_info.fields, into: %{} do
|
||||||
|
settings_key = Adapters.settings_key(adapter_key, field.key)
|
||||||
|
|
||||||
|
value =
|
||||||
|
case field.type do
|
||||||
|
:secret -> Settings.secret_hint(settings_key)
|
||||||
|
_ -> Settings.get_setting(settings_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
{field.key, value}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp provider_options do
|
defp provider_options do
|
||||||
Enum.map(Adapters.all(), fn adapter ->
|
Enum.map(Adapters.all(), fn adapter ->
|
||||||
%{
|
%{
|
||||||
@ -44,7 +64,8 @@ defmodule BerrypodWeb.Admin.EmailSettings do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("change_adapter", %{"email" => %{"adapter" => key}}, socket) do
|
def handle_event("change_adapter", %{"email" => %{"adapter" => key}}, socket) do
|
||||||
{:noreply, assign(socket, :adapter_key, key)}
|
values = load_adapter_values(key)
|
||||||
|
{:noreply, socket |> assign(:adapter_key, key) |> assign(:current_values, values)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("save", %{"email" => params}, socket) do
|
def handle_event("save", %{"email" => params}, socket) do
|
||||||
@ -113,8 +134,9 @@ defmodule BerrypodWeb.Admin.EmailSettings do
|
|||||||
|> Enum.filter(fn field ->
|
|> Enum.filter(fn field ->
|
||||||
val = params[field.key]
|
val = params[field.key]
|
||||||
empty = is_nil(val) or val == ""
|
empty = is_nil(val) or val == ""
|
||||||
|
settings_key = Adapters.settings_key(adapter_info.key, field.key)
|
||||||
# Secret fields can be left blank to keep existing value
|
# Secret fields can be left blank to keep existing value
|
||||||
empty and not (field.type == :secret and Settings.get_secret("email_#{field.key}") != nil)
|
empty and not (field.type == :secret and Settings.get_secret(settings_key) != nil)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if missing != [] do
|
if missing != [] do
|
||||||
@ -124,17 +146,10 @@ defmodule BerrypodWeb.Admin.EmailSettings do
|
|||||||
# Save adapter type
|
# Save adapter type
|
||||||
Settings.put_setting("email_adapter", adapter_info.key)
|
Settings.put_setting("email_adapter", adapter_info.key)
|
||||||
|
|
||||||
# Clear fields from other adapters
|
|
||||||
current_keys = MapSet.new(Enum.map(adapter_info.fields, &"email_#{&1.key}"))
|
|
||||||
|
|
||||||
for key <- Adapters.all_field_keys(), key not in current_keys do
|
|
||||||
Settings.delete_setting(key)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Save current adapter fields (blank secrets keep existing value)
|
# Save current adapter fields (blank secrets keep existing value)
|
||||||
for field <- adapter_info.fields do
|
for field <- adapter_info.fields do
|
||||||
value = params[field.key]
|
value = params[field.key]
|
||||||
settings_key = "email_#{field.key}"
|
settings_key = Adapters.settings_key(adapter_info.key, field.key)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
value && value != "" ->
|
value && value != "" ->
|
||||||
|
|||||||
@ -55,14 +55,14 @@ defmodule Berrypod.Mailer.AdaptersTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "field_keys/1" do
|
describe "field_keys/1" do
|
||||||
test "returns settings keys prefixed with email_" do
|
test "returns namespaced settings keys" do
|
||||||
smtp = Adapters.get("smtp")
|
smtp = Adapters.get("smtp")
|
||||||
keys = Adapters.field_keys(smtp)
|
keys = Adapters.field_keys(smtp)
|
||||||
|
|
||||||
assert "email_relay" in keys
|
assert "email_smtp_relay" in keys
|
||||||
assert "email_port" in keys
|
assert "email_smtp_port" in keys
|
||||||
assert "email_username" in keys
|
assert "email_smtp_username" in keys
|
||||||
assert "email_password" in keys
|
assert "email_smtp_password" in keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -70,9 +70,16 @@ defmodule Berrypod.Mailer.AdaptersTest do
|
|||||||
test "returns unique keys from all adapters" do
|
test "returns unique keys from all adapters" do
|
||||||
keys = Adapters.all_field_keys()
|
keys = Adapters.all_field_keys()
|
||||||
assert is_list(keys)
|
assert is_list(keys)
|
||||||
assert "email_api_key" in keys
|
assert "email_postmark_api_key" in keys
|
||||||
assert "email_relay" in keys
|
assert "email_smtp_relay" in keys
|
||||||
assert length(keys) == length(Enum.uniq(keys))
|
assert length(keys) == length(Enum.uniq(keys))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "settings_key/2" do
|
||||||
|
test "namespaces key with adapter" do
|
||||||
|
assert Adapters.settings_key("postmark", "api_key") == "email_postmark_api_key"
|
||||||
|
assert Adapters.settings_key("smtp", "relay") == "email_smtp_relay"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -26,7 +26,7 @@ defmodule Berrypod.MailerTest do
|
|||||||
describe "load_config/0" do
|
describe "load_config/0" do
|
||||||
test "loads adapter config from settings" do
|
test "loads adapter config from settings" do
|
||||||
Settings.put_setting("email_adapter", "postmark")
|
Settings.put_setting("email_adapter", "postmark")
|
||||||
Settings.put_secret("email_api_key", "pm_test_key_123")
|
Settings.put_secret("email_postmark_api_key", "pm_test_key_123")
|
||||||
|
|
||||||
Mailer.load_config()
|
Mailer.load_config()
|
||||||
|
|
||||||
@ -37,10 +37,10 @@ defmodule Berrypod.MailerTest do
|
|||||||
|
|
||||||
test "loads SMTP config with multiple fields" do
|
test "loads SMTP config with multiple fields" do
|
||||||
Settings.put_setting("email_adapter", "smtp")
|
Settings.put_setting("email_adapter", "smtp")
|
||||||
Settings.put_setting("email_relay", "smtp.example.com")
|
Settings.put_setting("email_smtp_relay", "smtp.example.com")
|
||||||
Settings.put_setting("email_port", 465, "integer")
|
Settings.put_setting("email_smtp_port", 465, "integer")
|
||||||
Settings.put_setting("email_username", "user@example.com")
|
Settings.put_setting("email_smtp_username", "user@example.com")
|
||||||
Settings.put_secret("email_password", "secret123")
|
Settings.put_secret("email_smtp_password", "secret123")
|
||||||
|
|
||||||
Mailer.load_config()
|
Mailer.load_config()
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ defmodule Berrypod.MailerTest do
|
|||||||
|
|
||||||
test "returns adapter key and config when configured from settings" do
|
test "returns adapter key and config when configured from settings" do
|
||||||
Settings.put_setting("email_adapter", "postmark")
|
Settings.put_setting("email_adapter", "postmark")
|
||||||
Settings.put_secret("email_api_key", "pm_test_key_123")
|
Settings.put_secret("email_postmark_api_key", "pm_test_key_123")
|
||||||
|
|
||||||
Mailer.load_config()
|
Mailer.load_config()
|
||||||
|
|
||||||
|
|||||||
@ -113,7 +113,7 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
|
|||||||
|
|
||||||
test "disconnect clears email configuration", %{conn: conn} do
|
test "disconnect clears email configuration", %{conn: conn} do
|
||||||
Settings.put_setting("email_adapter", "postmark")
|
Settings.put_setting("email_adapter", "postmark")
|
||||||
Settings.put_secret("email_api_key", "pm_test_abc")
|
Settings.put_secret("email_postmark_api_key", "pm_test_abc")
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
|
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
|
|||||||
|
|
||||||
test "shows test email section when configured", %{conn: conn} do
|
test "shows test email section when configured", %{conn: conn} do
|
||||||
Settings.put_setting("email_adapter", "postmark")
|
Settings.put_setting("email_adapter", "postmark")
|
||||||
Settings.put_secret("email_api_key", "pm_test_abc")
|
Settings.put_secret("email_postmark_api_key", "pm_test_abc")
|
||||||
|
|
||||||
{:ok, _view, html} = live(conn, ~p"/admin/settings/email")
|
{:ok, _view, html} = live(conn, ~p"/admin/settings/email")
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user