Packages

On-the-fly image resizing for Phoenix with caching and responsive components

Current section

Files

Jump to
phoenix_image lib phoenix_image.ex
Raw

lib/phoenix_image.ex

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