Skip to content

Commit e047376

Browse files
committed
Add extension_routes/1 similar to extension_messages/1
Also added overridable routes for email confirmation
1 parent a283fd2 commit e047376

File tree

8 files changed

+195
-17
lines changed

8 files changed

+195
-17
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v1.0.5 (TBA)
4+
5+
* Added `extension_messages/1` and `extension_routes/1` to extension controllers and callbacks
6+
* Added `PowEmailConfirmation.Phoenix.Routes.after_halted_registration_path/1` and `PowEmailConfirmation.Phoenix.Routes.after_halted_sign_in_path/1` routes
7+
38
## v1.0.4 (2019-03-13)
49

510
* Added `PowInvitation` to the `mix pow.extension.phoenix.gen.templates` and `mix pow.extension.phoenix.mailer.gen.templates` tasks

lib/extensions/email_confirmation/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Follow the instructions for extensions in [README.md](../../../README.md), and s
1212

1313
## Configuration
1414

15+
### Let user know when confirmation is required for changing the e-mail
16+
1517
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:
1618

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

27+
### Routes
28+
29+
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 registartion and sign in. These can be overridden in your custom `MyAppWeb.Pow.Routes` module.
30+
2531
## Prevent persistent session sign in
2632

2733
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.

lib/extensions/email_confirmation/phoenix/controllers/controller_callbacks.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ defmodule PowEmailConfirmation.Phoenix.ControllerCallbacks do
5858
end
5959
defp halt_unconfirmed(_user, _conn, success_response, _type), do: success_response
6060

61-
defp return_path(conn, :registration), do: routes(conn).after_registration_path(conn)
62-
defp return_path(conn, :session), do: routes(conn).after_sign_in_path(conn)
61+
defp return_path(conn, :registration), do: extension_routes(conn).after_halted_registration_path(conn)
62+
defp return_path(conn, :session), do: extension_routes(conn).after_halted_sign_in_path(conn)
6363

6464
@spec send_confirmation_email(map(), Conn.t()) :: any()
6565
def send_confirmation_email(user, conn) do
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule PowEmailConfirmation.Phoenix.Routes do
2+
@moduledoc """
3+
Module that handles routes.
4+
"""
5+
6+
alias PowEmailConfirmation.Phoenix.ConfirmationController
7+
8+
@doc """
9+
Path to redirect user to when user signs in, but e-mail hasn't been
10+
confirmed.
11+
12+
By default this is the same as the `after_sign_in_path/1`.
13+
"""
14+
def after_halted_sign_in_path(conn), do: ConfirmationController.routes(conn).after_sign_in_path(conn)
15+
16+
@doc """
17+
Path to redirect user to when user signs up, but e-mail hasn't been
18+
confirmed.
19+
20+
By default this is the same as the `after_registration_path/1`.
21+
"""
22+
def after_halted_registration_path(conn), do: ConfirmationController.routes(conn).after_registration_path(conn)
23+
end

lib/pow/extension/phoenix/controllers/controller/base.ex

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule Pow.Extension.Phoenix.Controller.Base do
1010
# ...
1111
end
1212
"""
13-
alias Pow.{Config, Phoenix.Controller, Phoenix.Routes}
13+
alias Pow.{Config, Phoenix.Controller}
1414

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

32+
@routes_fallback unquote(__MODULE__).__routes_fallback__(__MODULE__)
33+
3234
@doc false
33-
def routes(conn), do: Controller.routes(conn, Routes)
35+
def extension_routes(conn), do: unquote(__MODULE__).__routes_module__(conn, @routes_fallback)
3436
end
3537
end
3638

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

4547
@doc false
46-
def __messages_fallback__(module) do
47-
[_controller | base] =
48-
module
49-
|> Module.split()
50-
|> Enum.reverse()
51-
52-
[Messages]
53-
|> Enum.concat(base)
54-
|> Enum.reverse()
55-
|> Module.concat()
56-
end
48+
def __messages_fallback__(module), do: fallback(module, Messages)
5749

5850
# TODO: Remove config fallback by 1.1.0
5951
def __messages_fallback__(config, module, env) do
@@ -67,4 +59,27 @@ defmodule Pow.Extension.Phoenix.Controller.Base do
6759
module
6860
end
6961
end
62+
63+
@doc false
64+
def __routes_fallback__(module), do: fallback(module, Routes)
65+
66+
@doc false
67+
def __routes_module__(conn, fallback) do
68+
case Controller.routes(conn, fallback) do
69+
^fallback -> fallback
70+
routes -> Module.concat([routes, fallback])
71+
end
72+
end
73+
74+
defp fallback(controller, module) do
75+
[_controller | base] =
76+
controller
77+
|> Module.split()
78+
|> Enum.reverse()
79+
80+
[module]
81+
|> Enum.concat(base)
82+
|> Enum.reverse()
83+
|> Module.concat()
84+
end
7085
end

lib/pow/extension/phoenix/routes.ex

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
defmodule Pow.Extension.Phoenix.Routes do
2+
@moduledoc """
3+
Module that handles routes for extensions.
4+
5+
## Usage
6+
7+
defmodule MyAppWeb.Pow.Routes do
8+
use Pow.Phoenix.Routes
9+
use Pow.Extension.Phoenix.Routes,
10+
extensions: [PowExtensionOne, PowExtensionTwo]
11+
12+
alias MyAppWeb.Router.Helpers, as: Routes
13+
14+
def pow_extension_one_a_path(conn), do: Routes.some_path(conn, :index)
15+
end
16+
17+
Remember to update configuration with `routes_backend: MyAppWeb.Pow.Routes`.
18+
"""
19+
alias Pow.Extension
20+
21+
@doc false
22+
defmacro __using__(config) do
23+
quote do
24+
unquote(config)
25+
|> unquote(__MODULE__).__routes_modules__()
26+
|> Enum.map(&unquote(__MODULE__).__define_route_methods__/1)
27+
end
28+
end
29+
30+
@doc false
31+
def __routes_modules__(config) do
32+
Extension.Config.discover_modules(config, ["Phoenix", "Routes"])
33+
end
34+
35+
@doc false
36+
defmacro __define_route_methods__(extension) do
37+
quote do
38+
extension = unquote(extension)
39+
methods = extension.__info__(:functions)
40+
41+
for {fallback_method, 1} <- methods do
42+
method_name = unquote(__MODULE__).method_name(extension, fallback_method)
43+
unquote(__MODULE__).__define_route_method__(extension, method_name, fallback_method)
44+
end
45+
46+
unquote(__MODULE__).__define_fallback_module__(extension, methods)
47+
end
48+
end
49+
50+
@doc false
51+
defmacro __define_route_method__(extension, method_name, fallback_method) do
52+
quote bind_quoted: [extension: extension, method_name: method_name, fallback_method: fallback_method] do
53+
@spec unquote(method_name)(Conn.t()) :: binary()
54+
def unquote(method_name)(conn) do
55+
unquote(extension).unquote(fallback_method)(conn)
56+
end
57+
58+
defoverridable [{method_name, 1}]
59+
end
60+
end
61+
62+
@doc false
63+
defmacro __define_fallback_module__(extension, methods) do
64+
quote do
65+
name = Module.concat([__MODULE__, unquote(extension)])
66+
quoted = for {method, 1} <- unquote(methods) do
67+
method_name = unquote(__MODULE__).method_name(unquote(extension), method)
68+
69+
quote do
70+
@spec unquote(method)(Conn.t()) :: binary()
71+
def unquote(method)(conn) do
72+
unquote(__MODULE__).unquote(method_name)(conn)
73+
end
74+
end
75+
end
76+
77+
Module.create(name, quoted, Macro.Env.location(__ENV__))
78+
end
79+
end
80+
81+
@doc """
82+
Generates a namespaced method name for a route method.
83+
"""
84+
@spec method_name(atom(), atom()) :: atom()
85+
def method_name(extension, type) do
86+
namespace = namespace(extension)
87+
88+
String.to_atom("#{namespace}_#{type}")
89+
end
90+
91+
defp namespace(extension) do
92+
["Routes", "Phoenix" | base] =
93+
extension
94+
|> Module.split()
95+
|> Enum.reverse()
96+
97+
base
98+
|> Enum.reverse()
99+
|> Enum.join()
100+
|> Macro.underscore()
101+
end
102+
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule Pow.Extension.Phoenix.RoutesTest do
2+
defmodule Phoenix.Routes do
3+
def a_path(_conn), do: "/first"
4+
def b_path(_conn), do: "/second"
5+
end
6+
7+
defmodule Routes do
8+
use Pow.Extension.Phoenix.Routes,
9+
extensions: [Pow.Extension.Phoenix.RoutesTest]
10+
11+
def pow_extension_phoenix_routes_test_a_path(_conn), do: "/overridden"
12+
end
13+
14+
use ExUnit.Case
15+
doctest Pow.Extension.Phoenix.Routes
16+
17+
test "can override routes" do
18+
assert Routes.pow_extension_phoenix_routes_test_a_path(nil) == "/overridden"
19+
assert Routes.pow_extension_phoenix_routes_test_b_path(nil) == "/second"
20+
end
21+
22+
test "has fallback module" do
23+
assert Routes.Pow.Extension.Phoenix.RoutesTest.Phoenix.Routes.a_path(nil) == "/overridden"
24+
end
25+
end

test/support/extensions/mocks.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ defmodule Pow.Test.ExtensionMocks do
4949
__phoenix_views__(web_module)
5050
__conn_case__(web_module, cache_backend)
5151
__messages__(web_module, extensions)
52-
__routes__(web_module)
52+
__routes__(web_module, extensions)
5353

5454
quote do
5555
@config unquote(config)
@@ -191,10 +191,12 @@ defmodule Pow.Test.ExtensionMocks do
191191
Module.create(module, quoted, Macro.Env.location(__ENV__))
192192
end
193193

194-
def __routes__(web_module) do
194+
def __routes__(web_module, extensions) do
195195
module = Module.concat([web_module, Phoenix.Routes])
196196
quoted = quote do
197197
use Pow.Phoenix.Routes
198+
use Pow.Extension.Phoenix.Routes,
199+
extensions: unquote(extensions)
198200

199201
def after_sign_in_path(_conn), do: "/after_signed_in"
200202
def after_registration_path(_conn), do: "/after_registration"

0 commit comments

Comments
 (0)