defmodule PhoenixImage do @moduledoc """ On-the-fly image resizing for Phoenix with disk caching and responsive components. ## Setup 1. Configure your OTP app: ```elixir # config/config.exs config :phoenix_image, otp_app: :my_app ``` 2. Add to your supervision tree: ```elixir # lib/my_app/application.ex children = [ PhoenixImage, MyAppWeb.Endpoint ] ``` 3. Forward requests in your router: ```elixir # lib/my_app_web/router.ex forward "/resize", PhoenixImage.Plug ``` 4. Import the component: ```elixir # lib/my_app_web.ex, in html_helpers/0 import PhoenixImage.Component ``` ## Configuration All options are set via application config: config :phoenix_image, otp_app: :my_app, # Required prefix: "/resize", # Default: "/resize" secret: "random-secret", # Signing secret (falls back to endpoint's secret_key_base) quality: 90, # Default: 90 source_dir: "priv/static", # Default: "priv/static" cache_dir: "priv/static/cache/resize" # Default: "priv/static/cache/resize" """ @default_quality 90 @default_prefix "/resize" @default_source_dir "priv/static" @default_cache_dir "priv/static/cache/resize" @doc """ Returns a child spec for the `PhoenixImage.Dimensions` GenServer. Add `PhoenixImage` to your application's supervision tree to start the ETS-backed dimension cache. """ def child_spec(_opts) do %{ id: PhoenixImage.Dimensions, start: {PhoenixImage.Dimensions, :start_link, [[]]}, type: :worker } end @doc """ Returns the configured OTP app name. Raises if `:otp_app` is not configured. """ def otp_app do Application.get_env(:phoenix_image, :otp_app) || raise "phoenix_image :otp_app not configured" end @doc """ Returns the configured URL prefix. Default: `"/resize"`. Must match the `forward` path in your router. """ def prefix, do: Application.get_env(:phoenix_image, :prefix, @default_prefix) @doc """ Returns the signing secret used for URL signatures. Falls back to the Phoenix endpoint's `secret_key_base` if not configured. """ def secret do case Application.get_env(:phoenix_image, :secret) do nil -> app = otp_app() endpoint = Application.get_env(app, :endpoint) if endpoint do endpoint.config(:secret_key_base) else raise "phoenix_image :secret not configured and no endpoint found" end secret -> secret end end @doc """ Signs a URL path with HMAC-SHA256, returning a 12-character URL-safe base64 signature. """ def sign_url(path) do :crypto.mac(:hmac, :sha256, secret(), path) |> Base.url_encode64(padding: false) |> binary_part(0, 12) end @doc """ Verifies a URL path against a signature using constant-time comparison. Returns `:ok` if the signature is valid, `:error` otherwise. """ def verify_url(path, signature) do expected = sign_url(path) if Plug.Crypto.secure_compare(expected, signature), do: :ok, else: :error end @doc """ Returns the JPEG output quality (1-100). Default: `90`. """ def quality, do: Application.get_env(:phoenix_image, :quality, @default_quality) @doc """ Returns the source directory for original images, relative to the OTP app's priv. Default: `"priv/static"`. """ def source_dir, do: Application.get_env(:phoenix_image, :source_dir, @default_source_dir) @doc """ Returns the cache directory for resized images, relative to the OTP app's priv. Default: `"priv/static/cache/resize"`. """ def cache_dir, do: Application.get_env(:phoenix_image, :cache_dir, @default_cache_dir) end