feat: add smtp settings
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
c4135308e6
commit
a4f3aa5d6f
23 changed files with 2424 additions and 152 deletions
181
lib/mv/config.ex
181
lib/mv/config.ex
|
|
@ -451,40 +451,191 @@ defmodule Mv.Config do
|
|||
def oidc_only_env_set?, do: env_set?("OIDC_ONLY")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SMTP configuration (stubs for TDD – ENV overrides Settings; see docs/smtp-configuration-concept.md)
|
||||
# SMTP configuration – ENV overrides Settings; see docs/smtp-configuration-concept.md
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@doc "Returns SMTP host. ENV SMTP_HOST overrides Settings. Stub: always nil until implemented."
|
||||
@doc """
|
||||
Returns SMTP host. ENV `SMTP_HOST` overrides Settings.
|
||||
"""
|
||||
@spec smtp_host() :: String.t() | nil
|
||||
def smtp_host, do: nil
|
||||
def smtp_host do
|
||||
smtp_env_or_setting("SMTP_HOST", :smtp_host)
|
||||
end
|
||||
|
||||
@doc "Returns SMTP port (e.g. 587). ENV SMTP_PORT overrides Settings. Stub: always nil until implemented."
|
||||
@doc """
|
||||
Returns SMTP port as integer. ENV `SMTP_PORT` (parsed) overrides Settings.
|
||||
Returns nil when neither ENV nor Settings provide a valid port.
|
||||
"""
|
||||
@spec smtp_port() :: non_neg_integer() | nil
|
||||
def smtp_port, do: nil
|
||||
def smtp_port do
|
||||
case System.get_env("SMTP_PORT") do
|
||||
nil ->
|
||||
get_from_settings_integer(:smtp_port)
|
||||
|
||||
@doc "Returns SMTP username. ENV SMTP_USERNAME overrides Settings. Stub: always nil until implemented."
|
||||
value when is_binary(value) ->
|
||||
case Integer.parse(String.trim(value)) do
|
||||
{port, _} when port > 0 -> port
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns SMTP username. ENV `SMTP_USERNAME` overrides Settings.
|
||||
"""
|
||||
@spec smtp_username() :: String.t() | nil
|
||||
def smtp_username, do: nil
|
||||
def smtp_username do
|
||||
smtp_env_or_setting("SMTP_USERNAME", :smtp_username)
|
||||
end
|
||||
|
||||
@doc "Returns SMTP password. ENV SMTP_PASSWORD overrides SMTP_PASSWORD_FILE overrides Settings. Stub: always nil until implemented."
|
||||
@doc """
|
||||
Returns SMTP password.
|
||||
|
||||
Priority: `SMTP_PASSWORD` ENV > `SMTP_PASSWORD_FILE` (file contents) > Settings.
|
||||
Strips trailing whitespace/newlines from file contents.
|
||||
"""
|
||||
@spec smtp_password() :: String.t() | nil
|
||||
def smtp_password, do: nil
|
||||
def smtp_password do
|
||||
case System.get_env("SMTP_PASSWORD") do
|
||||
nil -> smtp_password_from_file_or_settings()
|
||||
value -> trim_nil(value)
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Returns SMTP TLS/SSL mode (e.g. 'tls', 'ssl', 'none'). Stub: always nil until implemented."
|
||||
defp smtp_password_from_file_or_settings do
|
||||
case System.get_env("SMTP_PASSWORD_FILE") do
|
||||
nil -> get_smtp_password_from_settings()
|
||||
path -> read_smtp_password_file(path)
|
||||
end
|
||||
end
|
||||
|
||||
defp read_smtp_password_file(path) do
|
||||
case File.read(String.trim(path)) do
|
||||
{:ok, content} -> trim_nil(content)
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns SMTP TLS/SSL mode string (e.g. 'tls', 'ssl', 'none').
|
||||
ENV `SMTP_SSL` overrides Settings.
|
||||
"""
|
||||
@spec smtp_ssl() :: String.t() | nil
|
||||
def smtp_ssl, do: nil
|
||||
def smtp_ssl do
|
||||
smtp_env_or_setting("SMTP_SSL", :smtp_ssl)
|
||||
end
|
||||
|
||||
@doc "Returns true when SMTP is configured (e.g. host present). Stub: always false until implemented."
|
||||
@doc """
|
||||
Returns true when SMTP is configured (host present from ENV or Settings).
|
||||
"""
|
||||
@spec smtp_configured?() :: boolean()
|
||||
def smtp_configured?, do: false
|
||||
def smtp_configured? do
|
||||
present?(smtp_host())
|
||||
end
|
||||
|
||||
@doc "Returns true when any SMTP ENV variable is set (for Settings UI hint). Stub: always false until implemented."
|
||||
@doc """
|
||||
Returns true when any SMTP ENV variable is set (used in Settings UI for hints).
|
||||
"""
|
||||
@spec smtp_env_configured?() :: boolean()
|
||||
def smtp_env_configured?, do: false
|
||||
def smtp_env_configured? do
|
||||
smtp_host_env_set?() or smtp_port_env_set?() or smtp_username_env_set?() or
|
||||
smtp_password_env_set?() or smtp_ssl_env_set?()
|
||||
end
|
||||
|
||||
@doc "Returns true if SMTP_HOST ENV is set."
|
||||
@spec smtp_host_env_set?() :: boolean()
|
||||
def smtp_host_env_set?, do: env_set?("SMTP_HOST")
|
||||
|
||||
@doc "Returns true if SMTP_PORT ENV is set."
|
||||
@spec smtp_port_env_set?() :: boolean()
|
||||
def smtp_port_env_set?, do: env_set?("SMTP_PORT")
|
||||
|
||||
@doc "Returns true if SMTP_USERNAME ENV is set."
|
||||
@spec smtp_username_env_set?() :: boolean()
|
||||
def smtp_username_env_set?, do: env_set?("SMTP_USERNAME")
|
||||
|
||||
@doc "Returns true if SMTP_PASSWORD or SMTP_PASSWORD_FILE ENV is set."
|
||||
@spec smtp_password_env_set?() :: boolean()
|
||||
def smtp_password_env_set?, do: env_set?("SMTP_PASSWORD") or env_set?("SMTP_PASSWORD_FILE")
|
||||
|
||||
@doc "Returns true if SMTP_SSL ENV is set."
|
||||
@spec smtp_ssl_env_set?() :: boolean()
|
||||
def smtp_ssl_env_set?, do: env_set?("SMTP_SSL")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Transactional email sender identity (mail_from)
|
||||
# ENV variables MAIL_FROM_NAME / MAIL_FROM_EMAIL take priority; fallback to
|
||||
# Settings smtp_from_name / smtp_from_email; final fallback: hardcoded defaults.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@doc """
|
||||
Returns the display name for the transactional email sender.
|
||||
|
||||
Priority: `MAIL_FROM_NAME` ENV > Settings `smtp_from_name` > `"Mila"`.
|
||||
"""
|
||||
@spec mail_from_name() :: String.t()
|
||||
def mail_from_name do
|
||||
case System.get_env("MAIL_FROM_NAME") do
|
||||
nil -> get_from_settings(:smtp_from_name) || "Mila"
|
||||
value -> trim_nil(value) || "Mila"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the email address for the transactional email sender.
|
||||
|
||||
Priority: `MAIL_FROM_EMAIL` ENV > Settings `smtp_from_email` > `nil`.
|
||||
Returns `nil` when not configured (caller should fall back to a safe default).
|
||||
"""
|
||||
@spec mail_from_email() :: String.t() | nil
|
||||
def mail_from_email do
|
||||
case System.get_env("MAIL_FROM_EMAIL") do
|
||||
nil -> get_from_settings(:smtp_from_email)
|
||||
value -> trim_nil(value)
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Returns true if MAIL_FROM_NAME ENV is set."
|
||||
@spec mail_from_name_env_set?() :: boolean()
|
||||
def mail_from_name_env_set?, do: env_set?("MAIL_FROM_NAME")
|
||||
|
||||
@doc "Returns true if MAIL_FROM_EMAIL ENV is set."
|
||||
@spec mail_from_email_env_set?() :: boolean()
|
||||
def mail_from_email_env_set?, do: env_set?("MAIL_FROM_EMAIL")
|
||||
|
||||
# Reads a plain string SMTP setting: ENV first, then Settings.
|
||||
defp smtp_env_or_setting(env_key, setting_key) do
|
||||
case System.get_env(env_key) do
|
||||
nil -> get_from_settings(setting_key)
|
||||
value -> trim_nil(value)
|
||||
end
|
||||
end
|
||||
|
||||
# Reads an integer setting attribute from Settings.
|
||||
defp get_from_settings_integer(key) do
|
||||
case Mv.Membership.get_settings() do
|
||||
{:ok, settings} ->
|
||||
case Map.get(settings, key) do
|
||||
v when is_integer(v) and v > 0 -> v
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
{:error, _} ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Reads the SMTP password directly from the DB via an explicit select,
|
||||
# bypassing the standard read action which excludes smtp_password for security.
|
||||
defp get_smtp_password_from_settings do
|
||||
query = Ash.Query.select(Mv.Membership.Setting, [:id, :smtp_password])
|
||||
|
||||
case Ash.read_one(query, authorize?: false, domain: Mv.Membership) do
|
||||
{:ok, settings} when not is_nil(settings) ->
|
||||
settings |> Map.get(:smtp_password) |> trim_nil()
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue