Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions lib/mix/tasks/phoenix_kit.status.ex
Original file line number Diff line number Diff line change
Expand Up @@ -416,12 +416,18 @@ defmodule Mix.Tasks.PhoenixKit.Status do
IO.puts("\n#{IO.ANSI.bright()}Configuration:#{IO.ANSI.reset()}")

# Check layout configuration
layout_config = Application.get_env(:phoenix_kit, :layout)
IO.puts(" Layout integration: #{if layout_config, do: "Configured", else: "Using defaults"}")
layout_config = PhoenixKit.Config.get(:layout)

IO.puts(
" Layout integration: #{if layout_config != :not_found, do: "Configured", else: "Using defaults"}"
)

# Check mailer configuration
mailer_config = Application.get_env(:phoenix_kit, PhoenixKit.Mailer)
IO.puts(" Mailer: #{if mailer_config, do: "Configured", else: "Not configured"}")
mailer_config = PhoenixKit.Config.get(PhoenixKit.Mailer)

IO.puts(
" Mailer: #{if mailer_config != :not_found, do: "Configured", else: "Not configured"}"
)
end

# Hybrid repo detection with fallback strategies
Expand Down
151 changes: 136 additions & 15 deletions lib/phoenix_kit/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ defmodule PhoenixKit.Config do
@moduledoc """
Configuration management system for PhoenixKit.

This module provides a centralized way to manage PhoenixKit configuration.
This module provides a centralized way to manage PhoenixKit configuration
with type-safe getter functions for different data types.

## Usage

Expand All @@ -13,6 +14,11 @@ defmodule PhoenixKit.Config do
repo = PhoenixKit.Config.get(:repo)
mailer = PhoenixKit.Config.get(:mailer, PhoenixKit.Mailer)

# Type-safe getters
options = PhoenixKit.Config.get_list(:options, [])
enabled = PhoenixKit.Config.get_boolean(:enabled, false)
host = PhoenixKit.Config.get_string(:host, "localhost")

## Configuration Keys

- `:repo` - Ecto repository module (required)
Expand All @@ -22,6 +28,15 @@ defmodule PhoenixKit.Config do
- `:layout_module` - Custom layout configuration
- `:from_email` - Default sender email address for notifications
- `:from_name` - Default sender name for notifications (default: "PhoenixKit")

## Type-Safe Functions

- `get_list/2` - Gets configuration values with list type validation
- `get_boolean/2` - Gets configuration values with boolean type validation
- `get_string/2` - Gets configuration values with string type validation

These functions provide automatic type validation and fallback to defaults
when the configuration value is missing or has the wrong type.
"""

@default_config [
Expand All @@ -38,7 +53,17 @@ defmodule PhoenixKit.Config do
from_email: nil,
from_name: "PhoenixKit",
magic_link_for_login_expiry_minutes: 15,
magic_link_for_registration_expiry_minutes: 30
magic_link_for_registration_expiry_minutes: 30,
# Security and authentication settings
password_requirements: [],
session_fingerprint_enabled: true,
session_fingerprint_strict: false,
secret_key_base: nil,
oauth_base_url: nil,
# Module-specific settings
blogging_settings_module: PhoenixKit.Settings,
# OAuth and third-party settings
ueberauth: []
]

@doc """
Expand Down Expand Up @@ -82,6 +107,72 @@ defmodule PhoenixKit.Config do
end
end

@doc """
Gets a configuration value as a list with type validation.

## Examples

iex> PhoenixKit.Config.get_list(:options, [])
[]

iex> PhoenixKit.Config.get_list(:nonexistent, [:default])
[:default]

"""
@spec get_list(atom(), list()) :: list()
def get_list(key, default \\ [])
when is_atom(key) and is_list(default) do
case get(key) do
{:ok, value} when is_list(value) -> value
{:ok, _} -> default
:not_found -> default
end
end

@doc """
Gets a configuration value as a boolean with type validation.

## Examples

iex> PhoenixKit.Config.get_boolean(:enabled, false)
true

iex> PhoenixKit.Config.get_boolean(:nonexistent, true)
true

"""
@spec get_boolean(atom(), boolean()) :: boolean()
def get_boolean(key, default \\ false)
when is_atom(key) and is_boolean(default) do
case get(key) do
{:ok, value} when is_boolean(value) -> value
{:ok, _} -> default
:not_found -> default
end
end

@doc """
Gets a configuration value as a string with type validation.

## Examples

iex> PhoenixKit.Config.get_string(:host, "localhost")
"example.com"

iex> PhoenixKit.Config.get_string(:nonexistent, "default")
"default"

"""
@spec get_string(atom(), String.t()) :: String.t()
def get_string(key, default \\ "")
when is_atom(key) and is_binary(default) do
case get(key) do
{:ok, value} when is_binary(value) -> value
{:ok, _} -> default
:not_found -> default
end
end

@doc """
Gets the configured mailer module.

Expand Down Expand Up @@ -130,17 +221,8 @@ defmodule PhoenixKit.Config do
"""
@spec get_base_url() :: String.t()
def get_base_url do
host =
case get(:host) do
{:ok, host} -> host
_ -> "localhost"
end

scheme =
case get(:scheme) do
{:ok, scheme} -> scheme
_ -> "http"
end
host = get_string(:host, "localhost")
scheme = get_string(:scheme, "http")

port =
case get(:port) do
Expand Down Expand Up @@ -231,13 +313,52 @@ defmodule PhoenixKit.Config do
"""
@spec get_url_prefix() :: String.t()
def get_url_prefix do
case get(:url_prefix, "/phoenix_kit") do
nil -> "/"
case get_string(:url_prefix, "/phoenix_kit") do
"" -> "/"
value -> value
end
end

@doc """
Gets the configured repository module.
"""
@spec get_repo() :: module() | nil
def get_repo do
case get(:repo) do
{:ok, repo} when is_atom(repo) -> repo
_ -> nil
end
end

@doc """
Gets the configured repository module, raising an error if not found.

## Examples

iex> PhoenixKit.Config.get_repo!()
MyApp.Repo

iex> PhoenixKit.Config.get_repo!()
** (ArgumentError) PhoenixKit repository not configured. Please set config :phoenix_kit, repo: YourApp.Repo

"""
@spec get_repo!() :: module()
def get_repo! do
case get(:repo) do
{:ok, repo} when is_atom(repo) ->
repo

_ ->
raise ArgumentError, """
PhoenixKit repository not configured. Please set:

config :phoenix_kit, repo: YourApp.Repo

in your application configuration.
"""
end
end

@doc """
Gets the parent application name that is using PhoenixKit.

Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_kit/emails/interceptor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ defmodule PhoenixKit.Emails.Interceptor do
case PhoenixKit.Config.get(:mailer) do
{:ok, mailer} when not is_nil(mailer) ->
# Try to determine provider from mailer configuration
config = Application.get_env(:phoenix_kit, mailer, [])
config = PhoenixKit.Config.get_list(mailer, [])
adapter = Keyword.get(config, :adapter)

case adapter do
Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_kit/mailer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ defmodule PhoenixKit.Mailer do

# Detect provider for built-in PhoenixKit mailer
defp detect_builtin_provider do
config = Application.get_env(:phoenix_kit, __MODULE__, [])
config = PhoenixKit.Config.get(PhoenixKit.Mailer, [])
adapter = Keyword.get(config, :adapter)
Utils.adapter_to_provider_name(adapter, "phoenix_kit_builtin")
end
Expand Down
4 changes: 2 additions & 2 deletions lib/phoenix_kit/migrations/postgres.ex
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ defmodule PhoenixKit.Migrations.Postgres do
# Hybrid repo detection with fallback strategies (shared with status command)
defp get_repo_with_fallback do
# Strategy 1: Try to get from PhoenixKit application config
case Application.get_env(:phoenix_kit, :repo) do
case PhoenixKit.Config.get_repo() do
nil ->
# Strategy 2: Try to ensure PhoenixKit application is started
case ensure_phoenix_kit_started() do
Expand All @@ -547,7 +547,7 @@ defmodule PhoenixKit.Migrations.Postgres do
# Try to start PhoenixKit application and get repo config
defp ensure_phoenix_kit_started do
Application.ensure_all_started(:phoenix_kit)
Application.get_env(:phoenix_kit, :repo)
PhoenixKit.Config.get_repo()
rescue
_ -> nil
end
Expand Down
3 changes: 1 addition & 2 deletions lib/phoenix_kit/storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1237,8 +1237,7 @@ defmodule PhoenixKit.Storage do
# ===== REPO HELPERS =====

defp repo do
# Get the repository from application config or use a default
Application.get_env(:phoenix_kit, :repo) || PhoenixKit.Repo
PhoenixKit.Config.get_repo()
end

# Query builders for file listing
Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_kit/storage/file_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,6 @@ defmodule PhoenixKit.Storage.FileServer do

@doc false
defp get_repo do
Application.get_env(:phoenix_kit, :repo) || raise "PhoenixKit repo not configured"
PhoenixKit.Config.get_repo!()
end
end
2 changes: 1 addition & 1 deletion lib/phoenix_kit/storage/url_signer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ defmodule PhoenixKit.Storage.URLSigner do
# 1. Explicitly configured on :phoenix_kit
# 2. From the configured endpoint
# 3. Return nil if not found (will use data without secret)
Application.get_env(:phoenix_kit, :secret_key_base) ||
PhoenixKit.Config.get(:secret_key_base, nil) ||
get_endpoint_secret()
end

Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_kit/users/auth/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ defmodule PhoenixKit.Users.Auth.User do
# - require_digit: false
# - require_special: false
defp apply_password_requirements(changeset) do
requirements = Application.get_env(:phoenix_kit, :password_requirements, [])
requirements = PhoenixKit.Config.get_list(:password_requirements, [])

changeset
|> validate_length(:password,
Expand Down
4 changes: 2 additions & 2 deletions lib/phoenix_kit/users/rate_limiter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,8 @@ defmodule PhoenixKit.Users.RateLimiter do
|> String.downcase()
end

defp get_config do
Application.get_env(:phoenix_kit, __MODULE__, [])
def get_config do
PhoenixKit.Config.get(__MODULE__, [])
|> Keyword.merge(@default_config, fn _k, v1, _v2 -> v1 end)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/phoenix_kit/utils/session_fingerprint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ defmodule PhoenixKit.Utils.SessionFingerprint do

"""
def fingerprinting_enabled? do
Application.get_env(:phoenix_kit, :session_fingerprint_enabled, true)
PhoenixKit.Config.get_boolean(:session_fingerprint_enabled, true)
end

@doc """
Expand All @@ -206,7 +206,7 @@ defmodule PhoenixKit.Utils.SessionFingerprint do

"""
def strict_mode? do
Application.get_env(:phoenix_kit, :session_fingerprint_strict, false)
PhoenixKit.Config.get_boolean(:session_fingerprint_strict, false)
end

# Private helper to get a header value from connection
Expand Down
6 changes: 3 additions & 3 deletions lib/phoenix_kit_web/live/users/media.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule PhoenixKitWeb.Live.Users.Media do

require Logger

import Ecto.Query

alias PhoenixKit.Settings
alias PhoenixKit.Storage.FileInstance
alias PhoenixKit.Storage.URLSigner
Expand Down Expand Up @@ -183,9 +185,7 @@ defmodule PhoenixKitWeb.Live.Users.Media do

# Load existing files from database with pagination
defp load_existing_files(page, per_page) do
import Ecto.Query

repo = Application.get_env(:phoenix_kit, :repo)
repo = PhoenixKit.Config.get_repo()

# Get total count
total_count = repo.aggregate(PhoenixKit.Storage.File, :count, :id)
Expand Down
8 changes: 3 additions & 5 deletions lib/phoenix_kit_web/live/users/media_detail.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule PhoenixKitWeb.Live.Users.MediaDetail do

require Logger

import Ecto.Query

alias PhoenixKit.Settings
alias PhoenixKit.Storage
alias PhoenixKit.Storage.File
Expand Down Expand Up @@ -96,7 +98,7 @@ defmodule PhoenixKitWeb.Live.Users.MediaDetail do
end

defp load_file_data(socket, file_id) do
repo = Application.get_env(:phoenix_kit, :repo)
repo = PhoenixKit.Config.get_repo()

case repo.get(File, file_id) do
nil ->
Expand All @@ -122,8 +124,6 @@ defmodule PhoenixKitWeb.Live.Users.MediaDetail do
end

defp load_file_instances(file_id, repo) do
import Ecto.Query

FileInstance
|> where([fi], fi.file_id == ^file_id)
|> repo.all()
Expand Down Expand Up @@ -186,8 +186,6 @@ defmodule PhoenixKitWeb.Live.Users.MediaDetail do

# Load file locations with bucket information
defp load_file_locations(file_instance_id, repo) do
import Ecto.Query

FileLocation
|> where([fl], fl.file_instance_id == ^file_instance_id and fl.status == "active")
|> preload(:bucket)
Expand Down
2 changes: 1 addition & 1 deletion lib/phoenix_kit_web/plugs/ensure_oauth_scheme.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ defmodule PhoenixKitWeb.Plugs.EnsureOAuthScheme do
apply_scheme(conn, forwarded_proto)

# 2. Check explicit oauth_base_url config
base_url = Application.get_env(:phoenix_kit, :oauth_base_url) ->
base_url = PhoenixKit.Config.get_string(:oauth_base_url) ->
apply_base_url(conn, base_url)

# 3. Check endpoint URL config
Expand Down
Loading