defmodule Curator do @type options :: Keyword.t() @callback before_sign_in( resource :: any, options :: options ) :: :ok | {:error, atom} @callback after_sign_in( conn :: Plug.Conn.t(), resource :: any, options :: options ) :: Plug.Conn.t() defmacro __using__(opts \\ []) do quote do use Curator.Config, unquote(opts) @behaviour Curator # Registered Extensions def before_sign_in(resource, opts \\ []) def before_sign_in(resource, opts), do: Curator.before_sign_in(__MODULE__, resource, opts) def after_sign_in(conn, resource, opts \\ []) def after_sign_in(conn, resource, opts), do: Curator.after_sign_in(__MODULE__, conn, resource, opts) # TODO # def after_failed_sign_in(conn, resource, opts \\ []) def extension(fun, args), do: Curator.extension(__MODULE__, fun, args) def changeset(fun, changeset, attrs), do: Curator.changeset(__MODULE__, fun, changeset, attrs) def deliver_email(fun, args), do: Curator.deliver_email(__MODULE__, fun, args) # Delegate to Guardian def sign_in(conn, resource, opts \\ []), do: Curator.sign_in(__MODULE__, conn, resource, opts) def sign_out(conn, opts \\ []), do: Curator.sign_out(__MODULE__, conn, opts) def current_resource(conn, opts \\ []), do: Curator.current_resource(__MODULE__, conn, opts) # Routing def store_return_to_url(conn), do: Curator.store_return_to_url(__MODULE__, conn) def redirect_after_sign_in(conn), do: Curator.redirect_after_sign_in(__MODULE__, conn) # def modules() do # @config_with_key_and_default :module, [] # end defoverridable before_sign_in: 2, after_sign_in: 3, redirect_after_sign_in: 1 end end # Extensions @doc """ apply before_sign_in to each module (until one fails) """ def before_sign_in(mod, resource, opts) do modules = modules(mod) Enum.reduce_while(modules, :ok, fn (module, :ok) -> case apply(module, :before_sign_in, [resource, opts]) do :ok -> {:cont, :ok} {:error, error} -> {:halt, {:error, error}} end end) end @doc """ apply after_sign_in to _all_ modules """ def after_sign_in(mod, conn, resource, opts) do modules = modules(mod) Enum.reduce(modules, conn, fn (module, conn) -> apply(module, :after_sign_in, [conn, resource, opts]) end) # Enum.reduce_while(modules, {:ok, conn}, fn (module) -> # case apply(module, :after_sign_in, [resource, opts]) do # {:ok, conn} -> {:cont, {:ok, conn}} # {:error, error} -> {:halt, {:error, error}} # end # end) end def store_return_to_url(_mod, conn) do url = case conn.query_string do "" -> conn.request_path _ -> conn.request_path <> "?" <> conn.query_string end Plug.Conn.put_session(conn, "user_return_to", url) end def redirect_after_sign_in(_mod, conn) do url = case Plug.Conn.get_session(conn, "user_return_to") do nil -> "/" value -> value end conn |> Plug.Conn.put_session("user_return_to", nil) |> Phoenix.Controller.redirect(to: url) end @doc """ Call an extension on all modules This provides a way to coordinate actions between the modules, without them knowing about eachother directly. Each module can have verious extensions it broadcasts. The other modules decide if they'll listen... """ def extension(mod, fun, args) do modules = modules(mod) Enum.each(modules, fn(module) -> arity = Enum.count(args) if function_exported?(module, fun, arity) do apply(module, fun, args) end end) end @doc """ Call a changeset on all modules Similar to an extension, this accumulates a changeset across various implementations """ def changeset(mod, fun, changeset, attrs) do modules = modules(mod) Enum.reduce(modules, changeset, fn (module, changeset) -> if function_exported?(module, fun, 2) do apply(module, fun, [changeset, attrs]) else changeset end end) end def deliver_email(mod, fun, args) do apply(email(mod), fun, args) |> mailer(mod).deliver() end # Delegate to Guardian def sign_in(mod, conn, resource, opts) do Module.concat(guardian_module(mod), Plug).sign_in(conn, resource, opts) end def sign_out(mod, conn, opts) do Module.concat(guardian_module(mod), Plug).sign_out(conn, opts) end def current_resource(mod, conn, opts) do Module.concat(guardian_module(mod), Plug).current_resource(conn, opts) end # Config def guardian_module(mod) do mod.config(:guardian) end def opaque_guardian(mod) do mod.config(:opaque_guardian) end def modules(mod) do mod.config(:modules) end def repo(mod) do mod.config(:repo) end def user(mod) do mod.config(:user) end def mailer(mod) do mod.config(:mailer) end def email(mod) do mod.config(:email) end end