Skip to content

Commit

Permalink
Add extension_routes/1 similar to extension_messages/1
Browse files Browse the repository at this point in the history
Also added overridable routes for email confirmation
  • Loading branch information
danschultzer committed Mar 16, 2019
1 parent a283fd2 commit 86cec5a
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 17 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v1.0.5 (TBA)

* Added `extension_messages/1` and `extension_routes/1` to extension controllers and callbacks
* Added `PowEmailConfirmation.Phoenix.Routes.after_halted_registration_path/1` and `PowEmailConfirmation.Phoenix.Routes.after_halted_sign_in_path/1` routes

## v1.0.4 (2019-03-13)

* Added `PowInvitation` to the `mix pow.extension.phoenix.gen.templates` and `mix pow.extension.phoenix.mailer.gen.templates` tasks
Expand Down
6 changes: 6 additions & 0 deletions lib/extensions/email_confirmation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Follow the instructions for extensions in [README.md](../../../README.md), and s

## Configuration

### Let user know when confirmation is required for changing the e-mail

Add the following section to your `WEB_PATH/templates/pow/registration/edit.html.eex` template (you may need to generate the templates first) after the e-mail field:

```elixir
Expand All @@ -22,6 +24,10 @@ Add the following section to your `WEB_PATH/templates/pow/registration/edit.html
<% end %>
```

### Routes

The `PowEmailConfirmation.Phoenix.Routes.after_halted_registration_path/1` and `PowEmailConfirmation.Phoenix.Routes.after_halted_sign_in_path/1` routes are used when halting unconfirmed e-mails registration and sign in. These can be overridden in your custom `MyAppWeb.Pow.Routes` module.

## Prevent persistent session sign in

To prevent that `PowPeristentSession` creates a new persistent session when the email hasn't been confirmed, `PowEmailConfirmation` should be placed first in the extensions list. It'll halt the connection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ defmodule PowEmailConfirmation.Phoenix.ControllerCallbacks do
end
defp halt_unconfirmed(_user, _conn, success_response, _type), do: success_response

defp return_path(conn, :registration), do: routes(conn).after_registration_path(conn)
defp return_path(conn, :session), do: routes(conn).after_sign_in_path(conn)
defp return_path(conn, :registration), do: extension_routes(conn).after_halted_registration_path(conn)
defp return_path(conn, :session), do: extension_routes(conn).after_halted_sign_in_path(conn)

@spec send_confirmation_email(map(), Conn.t()) :: any()
def send_confirmation_email(user, conn) do
Expand Down
23 changes: 23 additions & 0 deletions lib/extensions/email_confirmation/phoenix/routes.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule PowEmailConfirmation.Phoenix.Routes do
@moduledoc """
Module that handles routes.
"""

alias PowEmailConfirmation.Phoenix.ConfirmationController

@doc """
Path to redirect user to when user signs in, but e-mail hasn't been
confirmed.
By default this is the same as the `after_sign_in_path/1`.
"""
def after_halted_sign_in_path(conn), do: ConfirmationController.routes(conn).after_sign_in_path(conn)

@doc """
Path to redirect user to when user signs up, but e-mail hasn't been
confirmed.
By default this is the same as the `after_registration_path/1`.
"""
def after_halted_registration_path(conn), do: ConfirmationController.routes(conn).after_registration_path(conn)
end
41 changes: 28 additions & 13 deletions lib/pow/extension/phoenix/controllers/controller/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Pow.Extension.Phoenix.Controller.Base do
# ...
end
"""
alias Pow.{Config, Phoenix.Controller, Phoenix.Routes}
alias Pow.{Config, Phoenix.Controller}

@doc false
defmacro __using__(config) do
Expand All @@ -29,8 +29,10 @@ defmodule Pow.Extension.Phoenix.Controller.Base do
@doc false
def extension_messages(conn), do: unquote(__MODULE__).__messages_module__(conn, @messages_fallback)

@routes_fallback unquote(__MODULE__).__routes_fallback__(__MODULE__)

@doc false
def routes(conn), do: Controller.routes(conn, Routes)
def extension_routes(conn), do: unquote(__MODULE__).__routes_module__(conn, @routes_fallback)
end
end

Expand All @@ -43,17 +45,7 @@ defmodule Pow.Extension.Phoenix.Controller.Base do
end

@doc false
def __messages_fallback__(module) do
[_controller | base] =
module
|> Module.split()
|> Enum.reverse()

[Messages]
|> Enum.concat(base)
|> Enum.reverse()
|> Module.concat()
end
def __messages_fallback__(module), do: fallback(module, Messages)

# TODO: Remove config fallback by 1.1.0
def __messages_fallback__(config, module, env) do
Expand All @@ -67,4 +59,27 @@ defmodule Pow.Extension.Phoenix.Controller.Base do
module
end
end

@doc false
def __routes_fallback__(module), do: fallback(module, Routes)

@doc false
def __routes_module__(conn, fallback) do
case Controller.routes(conn, fallback) do
^fallback -> fallback
routes -> Module.concat([routes, fallback])
end
end

defp fallback(controller, module) do
[_controller | base] =
controller
|> Module.split()
|> Enum.reverse()

[module]
|> Enum.concat(base)
|> Enum.reverse()
|> Module.concat()
end
end
102 changes: 102 additions & 0 deletions lib/pow/extension/phoenix/routes.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
defmodule Pow.Extension.Phoenix.Routes do
@moduledoc """
Module that handles routes for extensions.
## Usage
defmodule MyAppWeb.Pow.Routes do
use Pow.Phoenix.Routes
use Pow.Extension.Phoenix.Routes,
extensions: [PowExtensionOne, PowExtensionTwo]
alias MyAppWeb.Router.Helpers, as: Routes
def pow_extension_one_a_path(conn), do: Routes.some_path(conn, :index)
end
Remember to update configuration with `routes_backend: MyAppWeb.Pow.Routes`.
"""
alias Pow.Extension

@doc false
defmacro __using__(config) do
quote do
unquote(config)
|> unquote(__MODULE__).__routes_modules__()
|> Enum.map(&unquote(__MODULE__).__define_route_methods__/1)
end
end

@doc false
def __routes_modules__(config) do
Extension.Config.discover_modules(config, ["Phoenix", "Routes"])
end

@doc false
defmacro __define_route_methods__(extension) do
quote do
extension = unquote(extension)
methods = extension.__info__(:functions)

for {fallback_method, 1} <- methods do
method_name = unquote(__MODULE__).method_name(extension, fallback_method)
unquote(__MODULE__).__define_route_method__(extension, method_name, fallback_method)
end

unquote(__MODULE__).__define_fallback_module__(extension, methods)
end
end

@doc false
defmacro __define_route_method__(extension, method_name, fallback_method) do
quote bind_quoted: [extension: extension, method_name: method_name, fallback_method: fallback_method] do
@spec unquote(method_name)(Conn.t()) :: binary()
def unquote(method_name)(conn) do
unquote(extension).unquote(fallback_method)(conn)
end

defoverridable [{method_name, 1}]
end
end

@doc false
defmacro __define_fallback_module__(extension, methods) do
quote do
name = Module.concat([__MODULE__, unquote(extension)])
quoted = for {method, 1} <- unquote(methods) do
method_name = unquote(__MODULE__).method_name(unquote(extension), method)

quote do
@spec unquote(method)(Conn.t()) :: binary()
def unquote(method)(conn) do
unquote(__MODULE__).unquote(method_name)(conn)
end
end
end

Module.create(name, quoted, Macro.Env.location(__ENV__))
end
end

@doc """
Generates a namespaced method name for a route method.
"""
@spec method_name(atom(), atom()) :: atom()
def method_name(extension, type) do
namespace = namespace(extension)

String.to_atom("#{namespace}_#{type}")
end

defp namespace(extension) do
["Routes", "Phoenix" | base] =
extension
|> Module.split()
|> Enum.reverse()

base
|> Enum.reverse()
|> Enum.join()
|> Macro.underscore()
end
end
25 changes: 25 additions & 0 deletions test/pow/extension/phoenix/routes_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Pow.Extension.Phoenix.RoutesTest do
defmodule Phoenix.Routes do
def a_path(_conn), do: "/first"
def b_path(_conn), do: "/second"
end

defmodule Routes do
use Pow.Extension.Phoenix.Routes,
extensions: [Pow.Extension.Phoenix.RoutesTest]

def pow_extension_phoenix_routes_test_a_path(_conn), do: "/overridden"
end

use ExUnit.Case
doctest Pow.Extension.Phoenix.Routes

test "can override routes" do
assert Routes.pow_extension_phoenix_routes_test_a_path(nil) == "/overridden"
assert Routes.pow_extension_phoenix_routes_test_b_path(nil) == "/second"
end

test "has fallback module" do
assert Routes.Pow.Extension.Phoenix.RoutesTest.Phoenix.Routes.a_path(nil) == "/overridden"
end
end
6 changes: 4 additions & 2 deletions test/support/extensions/mocks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ defmodule Pow.Test.ExtensionMocks do
__phoenix_views__(web_module)
__conn_case__(web_module, cache_backend)
__messages__(web_module, extensions)
__routes__(web_module)
__routes__(web_module, extensions)

quote do
@config unquote(config)
Expand Down Expand Up @@ -191,10 +191,12 @@ defmodule Pow.Test.ExtensionMocks do
Module.create(module, quoted, Macro.Env.location(__ENV__))
end

def __routes__(web_module) do
def __routes__(web_module, extensions) do
module = Module.concat([web_module, Phoenix.Routes])
quoted = quote do
use Pow.Phoenix.Routes
use Pow.Extension.Phoenix.Routes,
extensions: unquote(extensions)

def after_sign_in_path(_conn), do: "/after_signed_in"
def after_registration_path(_conn), do: "/after_registration"
Expand Down

0 comments on commit 86cec5a

Please sign in to comment.