Skip to content

Commit

Permalink
Add frontend controllers and views
Browse files Browse the repository at this point in the history
for user account management.

Needs to be enabled via

    # config/initializers/alchemy.rb
    Alchemy::Devise.enable_user_accounts = true
  • Loading branch information
tvdeyen committed Mar 17, 2020
1 parent dff0c4a commit 9813b2a
Show file tree
Hide file tree
Showing 29 changed files with 846 additions and 61 deletions.
18 changes: 18 additions & 0 deletions app/controllers/alchemy/accounts_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Alchemy
class AccountsController < ::Devise::RegistrationsController
helper 'Alchemy::Pages'

def show
authorize! :show, current_alchemy_user
@user = current_alchemy_user
end

private

def permission_denied(*)
store_location_for(:user, account_path)
flash[:warning] = t(:unauthenticated, scope: 'devise.failure')
redirect_to alchemy.login_path
end
end
end
11 changes: 11 additions & 0 deletions app/controllers/alchemy/confirmations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Alchemy
class ConfirmationsController < ::Devise::ConfirmationsController
helper 'Alchemy::Pages'

private

def new_session_path(*)
alchemy.login_path
end
end
end
11 changes: 11 additions & 0 deletions app/controllers/alchemy/passwords_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Alchemy
class PasswordsController < ::Devise::PasswordsController
helper 'Alchemy::Pages'

private

def new_session_path(*)
alchemy.login_path
end
end
end
11 changes: 11 additions & 0 deletions app/controllers/alchemy/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Alchemy
class SessionsController < ::Devise::SessionsController
helper 'Alchemy::Pages'

private

def after_sign_in_path_for(user)
stored_location_for(user) || alchemy.account_path
end
end
end
20 changes: 19 additions & 1 deletion app/mailers/alchemy/notifications.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Alchemy
class Notifications < ActionMailer::Base
class Notifications < ::Devise::Mailer

default(from: Config.get(:mailer)['mail_from'])

Expand All @@ -21,6 +21,15 @@ def alchemy_user_created(user)
)
end

def member_reset_password_instructions(user, token, opts={})
@user = user
@token = token
mail(
to: user.email,
subject: Alchemy.t("Reset password instructions")
)
end

def reset_password_instructions(user, token, opts={})
@user = user
@token = token
Expand All @@ -29,5 +38,14 @@ def reset_password_instructions(user, token, opts={})
subject: Alchemy.t("Reset password instructions")
)
end

def confirmation_instructions(user, token, opts={})
@user = user
@token = token
mail(
to: user.email,
subject: Alchemy.t("Account confirmation instructions")
)
end
end
end
9 changes: 9 additions & 0 deletions app/models/alchemy/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ def deliver_welcome_mail
end
end

# Overwritten to send a different email to members
def send_reset_password_instructions_notification(token)
if has_role?('member')
send_devise_notification(:member_reset_password_instructions, token, {})
else
send_devise_notification(:reset_password_instructions, token, {})
end
end

private

def logged_in_timeout
Expand Down
3 changes: 3 additions & 0 deletions app/views/alchemy/accounts/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<h2>Hallo <%= @user.fullname %></h2>

<%= link_to 'Edit account', alchemy.edit_account_path %>
15 changes: 15 additions & 0 deletions app/views/alchemy/devise/shared/_links.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%- if controller_name != 'sessions' %>
<%= link_to t('.login', default: 'Log in'), alchemy.login_path %><br />
<% end %>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to t('.sign_up', default: 'Sign up'), alchemy.new_account_path %><br />
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to t('.forgot_password', default: 'Forgot your password?'), alchemy.new_password_path %><br />
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to t('.confirmation_instructions', default: "Didn't receive confirmation instructions?"), alchemy.new_confirmation_path %><br />
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Willkommen <%= @user.fullname %>!

Bitte bestätigen Sie Ihre E-Mail-Adresse durch klicken auf folgenden Link:

<%= alchemy.confirmation_url(@user, confirmation_token: @token) %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Welcome <%= @user.fullname %>!

You can confirm your account email through the link below:

<%= alchemy.confirmation_url(@user, confirmation_token: @token) %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Hallo <%= @user.fullname %>.

Sie haben angefordert Ihr Passwort zurückzusetzen. Dies kann durch anklicken des nachfolgenden Links bestätigt werden.

<%= alchemy.edit_password_url(@user, reset_password_token: @token) %>

Wenn Sie diese Zurücksetzung nicht angefragt haben, dann können Sie diese E-Mail einfach ignorieren.
Ihr Passwort wird erst dann zurückgesetzt, wenn Sie den Link anklicken.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Hello <%= @user.name %>.

You have requested to change your password. Please confirm this by clicking the link below.

<%= alchemy.edit_password_url(@user, reset_password_token: @token) %>

If you didn't request this, please ignore this email.
Your password won't change until you access the link above and create a new one.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Hola <%= @user.name %>.

Has solicitado modificar tu contraseña. Por favor, confírmalo pulsando en el siguiente enlace.

<%= alchemy.edit_password_url(@user, reset_password_token: @token) %>

Si no has sido tu el que ha hecho la solicitud, ignora este correo.
Tu contraseña no cambiará hasta que no accedas al enlace de arriba y generes una nueva.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Здравствуйте, <%= @user.name %>.

Вы сделали запрос на смену пароля. Пожалуйста подтвердите это, нажав на ссылку ниже.

<%= alchemy.edit_password_url(@user, reset_password_token: @token) %>

Если вы не делали запрос, просто проигнорируйте это письмо.
Ваш пароль не изменится до тех пор, пока вы не перейдете по ссылке и сами не измените его.
27 changes: 27 additions & 0 deletions app/views/alchemy/passwords/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<h2><%= t('.title', default: 'Change your password') %></h2>

<%= simple_form_for(@user, as: :user, url: alchemy.password_path, html: { method: :put }) do |f| %>
<%= f.error_notification %>
<%= f.input :reset_password_token, as: :hidden %>
<%= f.full_error :reset_password_token %>

<div class="form-inputs">
<%= f.input :password,
label: t('.password.label', default: 'New password'),
required: true,
autofocus: true,
hint: t('.hint', default: '%{minimum} characters minimum', minimum: @minimum_password_length) if @minimum_password_length,
input_html: { autocomplete: "new-password" } %>
<%= f.input :password_confirmation,
label: t('.password_confirmation.label', default: 'Confirm your new password'),
required: true,
input_html: { autocomplete: "new-password" } %>
</div>

<div class="form-actions">
<%= f.button :submit, t('.button.label', default: 'Change my password') %>
</div>
<% end %>
<%= render "alchemy/devise/shared/links" %>
18 changes: 18 additions & 0 deletions app/views/alchemy/passwords/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<h2><%= t('.title', default: 'Forgot your password?') %></h2>

<%= simple_form_for(@user, as: :user, url: alchemy.password_path, html: { method: :post }) do |f| %>
<%= f.error_notification %>

<div class="form-inputs">
<%= f.input :email,
required: true,
autofocus: true,
input_html: { autocomplete: "email" } %>
</div>

<div class="form-actions">
<%= f.button :submit, t('.button.label', default: 'Send me reset password instructions') %>
</div>
<% end %>
<%= render "alchemy/devise/shared/links" %>
32 changes: 32 additions & 0 deletions app/views/alchemy/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<h2><%= t('.title', default: 'Log in') %></h2>

<% if flash[:alert] %>
<div class="alert alert-danger">
<%= flash[:alert] %>
</div>
<% end %>
<% if flash[:notice] %>
<div class="alert alert-info">
<%= flash[:notice] %>
</div>
<% end %>
<%= simple_form_for(@user, as: :user, url: alchemy.login_path) do |f| %>
<div class="form-inputs">
<%= f.input :email,
required: false,
autofocus: true,
input_html: { autocomplete: "email" } %>
<%= f.input :password,
required: false,
input_html: { autocomplete: "current-password" } %>
<%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
</div>

<div class="form-actions">
<%= f.button :submit, t('.button.label', default: 'Log in') %>
</div>
<% end %>
<%= render "alchemy/devise/shared/links" %>
32 changes: 32 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
Alchemy::Engine.routes.draw do
if Alchemy::Devise.enable_user_accounts?
devise_for :user,
class_name: 'Alchemy::User',
singular: :user,
skip: :all,
controllers: {
registrations: Alchemy::Devise.registrations_enabled? ? 'alchemy/accounts' : nil,
confirmations: Alchemy::Devise.confirmations_enabled? ? 'alchemy/confirmations' : nil,
sessions: 'alchemy/sessions',
passwords: 'alchemy/passwords'
},
path: :account,
router_name: :alchemy

scope :account do
devise_scope :user do
get '/login' => 'sessions#new'
post '/login' => 'sessions#create'
match '/logout' => 'sessions#destroy', via: Devise.sign_out_via

if Alchemy::Devise.confirmations_enabled?
resource :confirmation, only: %i[new create show]
end
resource :password, only: %i[new create edit update]
end
end

devise_scope :user do
resource :account, except: Alchemy::Devise.registrations_enabled? ? [] : %i[new create]
end
end

namespace :admin, {
path: Alchemy.admin_path,
constraints: Alchemy.admin_constraints
Expand Down
23 changes: 21 additions & 2 deletions lib/alchemy/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ module Alchemy
# === Default modules
#
# [
#. :database_authenticatable,
# :database_authenticatable,
# :trackable,
# :validatable,
# :timeoutable,
# :recoverable
#. ]
# ]
#
# If you want to add additional modules into the Alchemy user class append
# them to this collection in an initializer in your app.
Expand All @@ -36,6 +36,25 @@ def self.devise_modules
]
end

def self.devise_modules=(modules)
@devise_modules = modules
end

module Devise
def self.enable_user_accounts?
@enable_user_accounts ||= false
end

def self.enable_user_accounts=(val)
@enable_user_accounts = val
end

def self.registrations_enabled?
Alchemy.devise_modules.include?(:registerable)
end

def self.confirmations_enabled?
Alchemy.devise_modules.include?(:confirmable)
end
end
end
45 changes: 45 additions & 0 deletions spec/controllers/accounts_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'rails_helper'

describe Alchemy::AccountsController do
routes { Alchemy::Engine.routes }

context 'with user accounts enabled' do
before do
allow(Alchemy::Devise).to receive(:enable_user_accounts?) { true }
Rails.application.reload_routes!
@request.env["devise.mapping"] = Devise.mappings[:user]
end

describe '#show' do
let(:user) { create(:alchemy_member_user) }

context 'with authorized user' do
before { authorize_user(user) }

render_views

it 'shows account' do
get :show
is_expected.to render_template(:show)
end
end

context 'with unauthorized user' do
it 'redirects to login' do
get :show
is_expected.to redirect_to(login_path)
end

it 'stores current location' do
get :show
expect(session[:user_return_to]).to eq(account_path)
end

it 'shows warning message' do
get :show
expect(flash[:warning]).to eq I18n.t(:unauthenticated, scope: 'devise.failure')
end
end
end
end
end
Loading

0 comments on commit 9813b2a

Please sign in to comment.