641 lines
19 KiB
Elixir
641 lines
19 KiB
Elixir
defmodule Mv.Config do
|
||
@moduledoc """
|
||
Configuration helper functions for the application.
|
||
|
||
Provides centralized access to configuration values to avoid
|
||
magic strings/atoms scattered throughout the codebase.
|
||
"""
|
||
|
||
@doc """
|
||
Returns whether SQL sandbox mode is enabled.
|
||
|
||
SQL sandbox mode is typically enabled in test environments
|
||
to allow concurrent database access in tests.
|
||
|
||
## Returns
|
||
|
||
- `true` if SQL sandbox is enabled
|
||
- `false` otherwise
|
||
"""
|
||
@spec sql_sandbox?() :: boolean()
|
||
def sql_sandbox? do
|
||
Application.get_env(:mv, :sql_sandbox, false)
|
||
end
|
||
|
||
@doc """
|
||
Returns the maximum file size for CSV imports in bytes.
|
||
|
||
Reads the `max_file_size_mb` value from the CSV import configuration
|
||
and converts it to bytes.
|
||
|
||
## Returns
|
||
|
||
- Maximum file size in bytes (default: 10_485_760 bytes = 10 MB)
|
||
|
||
## Examples
|
||
|
||
iex> Mv.Config.csv_import_max_file_size_bytes()
|
||
10_485_760
|
||
"""
|
||
@spec csv_import_max_file_size_bytes() :: non_neg_integer()
|
||
def csv_import_max_file_size_bytes do
|
||
max_file_size_mb = get_csv_import_config(:max_file_size_mb, 10)
|
||
max_file_size_mb * 1024 * 1024
|
||
end
|
||
|
||
@doc """
|
||
Returns the maximum number of rows allowed in CSV imports.
|
||
|
||
Reads the `max_rows` value from the CSV import configuration.
|
||
|
||
## Returns
|
||
|
||
- Maximum number of rows (default: 1000)
|
||
|
||
## Examples
|
||
|
||
iex> Mv.Config.csv_import_max_rows()
|
||
1000
|
||
"""
|
||
@spec csv_import_max_rows() :: pos_integer()
|
||
def csv_import_max_rows do
|
||
get_csv_import_config(:max_rows, 1000)
|
||
end
|
||
|
||
@doc """
|
||
Returns the maximum file size for CSV imports in megabytes.
|
||
|
||
Reads the `max_file_size_mb` value from the CSV import configuration.
|
||
|
||
## Returns
|
||
|
||
- Maximum file size in megabytes (default: 10)
|
||
|
||
## Examples
|
||
|
||
iex> Mv.Config.csv_import_max_file_size_mb()
|
||
10
|
||
"""
|
||
@spec csv_import_max_file_size_mb() :: pos_integer()
|
||
def csv_import_max_file_size_mb do
|
||
get_csv_import_config(:max_file_size_mb, 10)
|
||
end
|
||
|
||
# Helper function to get CSV import config values
|
||
defp get_csv_import_config(key, default) do
|
||
Application.get_env(:mv, :csv_import, [])
|
||
|> Keyword.get(key, default)
|
||
|> parse_and_validate_integer(default)
|
||
end
|
||
|
||
# Parses and validates integer configuration values.
|
||
#
|
||
# Accepts:
|
||
# - Integer values (passed through)
|
||
# - String integers (e.g., "1000") - parsed to integer
|
||
# - Invalid values (e.g., "abc", nil) - falls back to default
|
||
#
|
||
# Always clamps the result to a minimum of 1 to ensure positive values.
|
||
#
|
||
# Note: We don't log warnings for unparseable values because:
|
||
# - These functions may be called frequently (e.g., on every request)
|
||
# - Logging would create excessive log spam
|
||
# - The fallback to default provides a safe behavior
|
||
# - Configuration errors should be caught during deployment/testing
|
||
defp parse_and_validate_integer(value, _default) when is_integer(value) do
|
||
max(1, value)
|
||
end
|
||
|
||
defp parse_and_validate_integer(value, default) when is_binary(value) do
|
||
case Integer.parse(value) do
|
||
{int, _remainder} -> max(1, int)
|
||
:error -> default
|
||
end
|
||
end
|
||
|
||
defp parse_and_validate_integer(_value, default) do
|
||
default
|
||
end
|
||
|
||
@doc """
|
||
Returns the maximum number of rows allowed in PDF exports.
|
||
|
||
Reads the `row_limit` value from the PDF export configuration.
|
||
|
||
## Returns
|
||
|
||
- Maximum number of rows (default: 5000)
|
||
|
||
## Examples
|
||
|
||
iex> Mv.Config.pdf_export_row_limit()
|
||
5000
|
||
"""
|
||
@spec pdf_export_row_limit() :: pos_integer()
|
||
def pdf_export_row_limit do
|
||
get_pdf_export_config(:row_limit, 5000)
|
||
end
|
||
|
||
# Helper function to get PDF export config values
|
||
defp get_pdf_export_config(key, default) do
|
||
Application.get_env(:mv, :pdf_export, [])
|
||
|> Keyword.get(key, default)
|
||
|> parse_and_validate_integer(default)
|
||
end
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Vereinfacht accounting software integration
|
||
# ENV variables take priority; fallback to Settings from database.
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@doc """
|
||
Returns the Vereinfacht API base URL.
|
||
|
||
Reads from `VEREINFACHT_API_URL` env first, then from Settings.
|
||
"""
|
||
@spec vereinfacht_api_url() :: String.t() | nil
|
||
def vereinfacht_api_url do
|
||
env_or_setting("VEREINFACHT_API_URL", :vereinfacht_api_url)
|
||
end
|
||
|
||
@doc """
|
||
Returns the Vereinfacht API key (Bearer token).
|
||
|
||
Reads from `VEREINFACHT_API_KEY` env first, then from Settings.
|
||
"""
|
||
@spec vereinfacht_api_key() :: String.t() | nil
|
||
def vereinfacht_api_key do
|
||
env_or_setting("VEREINFACHT_API_KEY", :vereinfacht_api_key)
|
||
end
|
||
|
||
@doc """
|
||
Returns the Vereinfacht club ID for multi-tenancy.
|
||
|
||
Reads from `VEREINFACHT_CLUB_ID` env first, then from Settings.
|
||
"""
|
||
@spec vereinfacht_club_id() :: String.t() | nil
|
||
def vereinfacht_club_id do
|
||
env_or_setting("VEREINFACHT_CLUB_ID", :vereinfacht_club_id)
|
||
end
|
||
|
||
@doc """
|
||
Returns the Vereinfacht app base URL for contact view links (frontend, not API).
|
||
|
||
Reads from `VEREINFACHT_APP_URL` env first, then from Settings.
|
||
Used to build links like https://app.verein.visuel.dev/en/admin/finances/contacts/{id}.
|
||
If not set, derived from API URL by replacing host \"api.\" with \"app.\" when possible.
|
||
"""
|
||
@spec vereinfacht_app_url() :: String.t() | nil
|
||
def vereinfacht_app_url do
|
||
env_or_setting("VEREINFACHT_APP_URL", :vereinfacht_app_url) ||
|
||
derive_app_url_from_api_url(vereinfacht_api_url())
|
||
end
|
||
|
||
defp derive_app_url_from_api_url(nil), do: nil
|
||
|
||
defp derive_app_url_from_api_url(api_url) when is_binary(api_url) do
|
||
api_url = String.trim(api_url)
|
||
uri = URI.parse(api_url)
|
||
host = uri.host || ""
|
||
|
||
if String.starts_with?(host, "api.") do
|
||
app_host = "app." <> String.slice(host, 4..-1//1)
|
||
scheme = uri.scheme || "https"
|
||
"#{scheme}://#{app_host}"
|
||
else
|
||
nil
|
||
end
|
||
end
|
||
|
||
defp derive_app_url_from_api_url(_), do: nil
|
||
|
||
@doc """
|
||
Returns true if Vereinfacht is fully configured (URL, API key, and club ID all set).
|
||
"""
|
||
@spec vereinfacht_configured?() :: boolean()
|
||
def vereinfacht_configured? do
|
||
present?(vereinfacht_api_url()) and present?(vereinfacht_api_key()) and
|
||
present?(vereinfacht_club_id())
|
||
end
|
||
|
||
@doc """
|
||
Returns true if any Vereinfacht ENV variable is set (used to show hint in Settings UI).
|
||
"""
|
||
@spec vereinfacht_env_configured?() :: boolean()
|
||
def vereinfacht_env_configured? do
|
||
vereinfacht_api_url_env_set?() or vereinfacht_api_key_env_set?() or
|
||
vereinfacht_club_id_env_set?()
|
||
end
|
||
|
||
@doc """
|
||
Returns true if VEREINFACHT_API_URL is set (field is read-only in Settings).
|
||
"""
|
||
def vereinfacht_api_url_env_set?, do: env_set?("VEREINFACHT_API_URL")
|
||
|
||
@doc """
|
||
Returns true if VEREINFACHT_API_KEY is set (field is read-only in Settings).
|
||
"""
|
||
def vereinfacht_api_key_env_set?, do: env_set?("VEREINFACHT_API_KEY")
|
||
|
||
@doc """
|
||
Returns true if VEREINFACHT_CLUB_ID is set (field is read-only in Settings).
|
||
"""
|
||
def vereinfacht_club_id_env_set?, do: env_set?("VEREINFACHT_CLUB_ID")
|
||
|
||
@doc """
|
||
Returns true if VEREINFACHT_APP_URL is set (field is read-only in Settings).
|
||
"""
|
||
def vereinfacht_app_url_env_set?, do: env_set?("VEREINFACHT_APP_URL")
|
||
|
||
defp env_set?(key) do
|
||
case System.get_env(key) do
|
||
nil -> false
|
||
v when is_binary(v) -> String.trim(v) != ""
|
||
_ -> false
|
||
end
|
||
end
|
||
|
||
defp env_or_setting(env_key, setting_key) do
|
||
case System.get_env(env_key) do
|
||
nil -> get_vereinfacht_from_settings(setting_key)
|
||
value -> trim_nil(value)
|
||
end
|
||
end
|
||
|
||
defp env_or_setting_bool(env_key, setting_key) do
|
||
case System.get_env(env_key) do
|
||
nil ->
|
||
get_from_settings_bool(setting_key)
|
||
|
||
value when is_binary(value) ->
|
||
v = String.trim(value) |> String.downcase()
|
||
v in ["true", "1", "yes"]
|
||
|
||
_ ->
|
||
false
|
||
end
|
||
end
|
||
|
||
defp get_vereinfacht_from_settings(key) do
|
||
get_from_settings(key)
|
||
end
|
||
|
||
defp get_from_settings(key) do
|
||
case Mv.Membership.get_settings() do
|
||
{:ok, settings} -> settings |> Map.get(key) |> trim_nil()
|
||
{:error, _} -> nil
|
||
end
|
||
end
|
||
|
||
defp get_from_settings_bool(key) do
|
||
case Mv.Membership.get_settings() do
|
||
{:ok, settings} ->
|
||
case Map.get(settings, key) do
|
||
true -> true
|
||
_ -> false
|
||
end
|
||
|
||
{:error, _} ->
|
||
false
|
||
end
|
||
end
|
||
|
||
defp trim_nil(nil), do: nil
|
||
|
||
defp trim_nil(s) when is_binary(s) do
|
||
t = String.trim(s)
|
||
if t == "", do: nil, else: t
|
||
end
|
||
|
||
@doc """
|
||
Returns the URL to view a finance contact in the Vereinfacht app (frontend).
|
||
|
||
Uses the configured app base URL (or derived from API URL) and appends
|
||
/en/admin/finances/contacts/{id}. Returns nil if no app URL can be determined.
|
||
"""
|
||
@spec vereinfacht_contact_view_url(String.t()) :: String.t() | nil
|
||
def vereinfacht_contact_view_url(contact_id) when is_binary(contact_id) do
|
||
base = vereinfacht_app_url()
|
||
|
||
if present?(base) do
|
||
base
|
||
|> String.trim_trailing("/")
|
||
|> then(&"#{&1}/en/admin/finances/contacts/#{contact_id}")
|
||
else
|
||
nil
|
||
end
|
||
end
|
||
|
||
defp present?(nil), do: false
|
||
defp present?(s) when is_binary(s), do: String.trim(s) != ""
|
||
defp present?(_), do: false
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# OIDC authentication
|
||
# ENV variables take priority; fallback to Settings from database.
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@doc """
|
||
Returns the OIDC client ID. ENV first, then Settings.
|
||
"""
|
||
@spec oidc_client_id() :: String.t() | nil
|
||
def oidc_client_id do
|
||
env_or_setting("OIDC_CLIENT_ID", :oidc_client_id)
|
||
end
|
||
|
||
@doc """
|
||
Returns the OIDC provider base URL. ENV first, then Settings.
|
||
"""
|
||
@spec oidc_base_url() :: String.t() | nil
|
||
def oidc_base_url do
|
||
env_or_setting("OIDC_BASE_URL", :oidc_base_url)
|
||
end
|
||
|
||
@doc """
|
||
Returns the OIDC redirect URI. ENV first, then Settings.
|
||
"""
|
||
@spec oidc_redirect_uri() :: String.t() | nil
|
||
def oidc_redirect_uri do
|
||
env_or_setting("OIDC_REDIRECT_URI", :oidc_redirect_uri)
|
||
end
|
||
|
||
@doc """
|
||
Returns the OIDC client secret.
|
||
In production, uses the value from config :mv, :oidc (set by runtime.exs from OIDC_CLIENT_SECRET or OIDC_CLIENT_SECRET_FILE).
|
||
Otherwise ENV OIDC_CLIENT_SECRET, then Settings.
|
||
"""
|
||
@spec oidc_client_secret() :: String.t() | nil
|
||
def oidc_client_secret do
|
||
case Application.get_env(:mv, :oidc) do
|
||
oidc when is_list(oidc) -> oidc_client_secret_from_config(Keyword.get(oidc, :client_secret))
|
||
_ -> env_or_setting("OIDC_CLIENT_SECRET", :oidc_client_secret)
|
||
end
|
||
end
|
||
|
||
defp oidc_client_secret_from_config(nil),
|
||
do: env_or_setting("OIDC_CLIENT_SECRET", :oidc_client_secret)
|
||
|
||
defp oidc_client_secret_from_config(secret) when is_binary(secret) do
|
||
s = String.trim(secret)
|
||
if s != "", do: s, else: env_or_setting("OIDC_CLIENT_SECRET", :oidc_client_secret)
|
||
end
|
||
|
||
defp oidc_client_secret_from_config(_),
|
||
do: env_or_setting("OIDC_CLIENT_SECRET", :oidc_client_secret)
|
||
|
||
@doc """
|
||
Returns the OIDC admin group name (for role sync). ENV first, then Settings.
|
||
"""
|
||
@spec oidc_admin_group_name() :: String.t() | nil
|
||
def oidc_admin_group_name do
|
||
env_or_setting("OIDC_ADMIN_GROUP_NAME", :oidc_admin_group_name)
|
||
end
|
||
|
||
@doc """
|
||
Returns the OIDC groups claim name (default "groups"). ENV first, then Settings.
|
||
"""
|
||
@spec oidc_groups_claim() :: String.t() | nil
|
||
def oidc_groups_claim do
|
||
case env_or_setting("OIDC_GROUPS_CLAIM", :oidc_groups_claim) do
|
||
nil -> "groups"
|
||
v -> v
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns true if any OIDC ENV variable is set (used to show hint in Settings UI).
|
||
"""
|
||
@spec oidc_env_configured?() :: boolean()
|
||
def oidc_env_configured? do
|
||
oidc_client_id_env_set?() or oidc_base_url_env_set?() or
|
||
oidc_redirect_uri_env_set?() or oidc_client_secret_env_set?() or
|
||
oidc_admin_group_name_env_set?() or oidc_groups_claim_env_set?() or
|
||
oidc_only_env_set?()
|
||
end
|
||
|
||
@doc """
|
||
Returns true when OIDC is configured and can be used for sign-in (client ID, base URL,
|
||
redirect URI, and client secret must be set). Used to show or hide the Single Sign-On button on the
|
||
sign-in page. Without client secret, the OIDC flow fails with MissingSecret; without redirect_uri,
|
||
the OIDC Plug crashes with URI.new(nil).
|
||
"""
|
||
@spec oidc_configured?() :: boolean()
|
||
def oidc_configured? do
|
||
id = oidc_client_id()
|
||
base = oidc_base_url()
|
||
secret = oidc_client_secret()
|
||
redirect = oidc_redirect_uri()
|
||
present = &(is_binary(&1) and String.trim(&1) != "")
|
||
present.(id) and present.(base) and present.(secret) and present.(redirect)
|
||
end
|
||
|
||
@doc """
|
||
Returns true when only OIDC sign-in should be shown (password login hidden).
|
||
ENV OIDC_ONLY first (true/1/yes vs false/0/no), then Settings.oidc_only.
|
||
Only has effect when OIDC is configured; when false or OIDC not configured, both password and OIDC are shown as usual.
|
||
"""
|
||
@spec oidc_only?() :: boolean()
|
||
def oidc_only? do
|
||
env_or_setting_bool("OIDC_ONLY", :oidc_only)
|
||
end
|
||
|
||
def oidc_client_id_env_set?, do: env_set?("OIDC_CLIENT_ID")
|
||
def oidc_base_url_env_set?, do: env_set?("OIDC_BASE_URL")
|
||
def oidc_redirect_uri_env_set?, do: env_set?("OIDC_REDIRECT_URI")
|
||
|
||
def oidc_client_secret_env_set?,
|
||
do: env_set?("OIDC_CLIENT_SECRET") or env_set?("OIDC_CLIENT_SECRET_FILE")
|
||
|
||
def oidc_admin_group_name_env_set?, do: env_set?("OIDC_ADMIN_GROUP_NAME")
|
||
def oidc_groups_claim_env_set?, do: env_set?("OIDC_GROUPS_CLAIM")
|
||
def oidc_only_env_set?, do: env_set?("OIDC_ONLY")
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# SMTP configuration – ENV overrides Settings; see docs/smtp-configuration-concept.md
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@doc """
|
||
Returns SMTP host. ENV `SMTP_HOST` overrides Settings.
|
||
"""
|
||
@spec smtp_host() :: String.t() | nil
|
||
def smtp_host do
|
||
smtp_env_or_setting("SMTP_HOST", :smtp_host)
|
||
end
|
||
|
||
@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
|
||
case System.get_env("SMTP_PORT") do
|
||
nil ->
|
||
get_from_settings_integer(:smtp_port)
|
||
|
||
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
|
||
smtp_env_or_setting("SMTP_USERNAME", :smtp_username)
|
||
end
|
||
|
||
@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
|
||
case System.get_env("SMTP_PASSWORD") do
|
||
nil -> smtp_password_from_file_or_settings()
|
||
value -> trim_nil(value)
|
||
end
|
||
end
|
||
|
||
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
|
||
smtp_env_or_setting("SMTP_SSL", :smtp_ssl)
|
||
end
|
||
|
||
@doc """
|
||
Returns true when SMTP is configured (host present from ENV or Settings).
|
||
"""
|
||
@spec smtp_configured?() :: boolean()
|
||
def smtp_configured? do
|
||
present?(smtp_host())
|
||
end
|
||
|
||
@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
|
||
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
|