Skip to content

Commit

Permalink
Streamline organization onboarding using a newly introduced account r…
Browse files Browse the repository at this point in the history
…equest flow (#1788)

* Add AccountRequest model + specs & migration

* Enable creating account_requests

* Redirect to confirmation page when the account_request was succesful

* Add case to handle invalid token

* Add AccountRequestMailer & confirmation mail

* Add routing specs for AccountRequestsController

* Just about ready functionally

* Fix spec with the mailing

* Functional!

* Allow mailer jobs to be processed off the `default` queue

* Update copies

* Change route name

* Update styling on pages

* Keep track of confirmed account requests

* Functional!

* Flash an error message if trying to use the same account request

* Add more account_request request specs

* Add admin request specs

* Add system spec

* System test for Account Request flow

* Fix rubocop complaints

* Add back in fakeredis

* Remove unneeded templates

* Update copy

* Fix broken specs

* Fix broken specs

* Update app/views/static/index.html.erb

Co-authored-by: Aaron H <aaron@rubyforgood.org>

* Update app/views/account_requests/confirmation.html.erb

Co-authored-by: Aaron H <aaron@rubyforgood.org>

* Update app/views/account_requests/confirmation.html.erb

Co-authored-by: Aaron H <aaron@rubyforgood.org>

* Update app/views/account_request_mailer/confirmation.html.erb

Co-authored-by: Aaron H <aaron@rubyforgood.org>

* Update spec

* Update based on PR suggestion

* Fix rubocop

* Fix broken spec

Co-authored-by: Aaron H <aaron@rubyforgood.org>
  • Loading branch information
edwinthinks and armahillo authored Sep 19, 2020
1 parent 8f1fc0a commit 2ecdbd0
Show file tree
Hide file tree
Showing 33 changed files with 989 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ gem "image_processing"
gem "jbuilder"
gem "jquery-rails"
gem "jquery-ui-rails"
gem "jwt"
gem "kaminari"
gem "mini_racer", "~> 0.3.1"
gem "momentjs-rails"
Expand Down Expand Up @@ -96,6 +97,7 @@ group :test do
gem "rails-controller-testing"
gem "rspec-sidekiq"
gem 'simplecov'
gem 'shoulda-matchers', '~> 4.0'
gem 'webdrivers', '~> 4.4'
gem "webmock", "~> 3.9"
end
Expand Down
5 changes: 5 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ GEM
thor (>= 0.14, < 2.0)
jquery-ui-rails (6.0.1)
railties (>= 3.2.16)
jwt (2.2.1)
kaminari (1.2.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.1)
Expand Down Expand Up @@ -424,6 +425,8 @@ GEM
rubyzip (>= 1.2.2)
semantic_range (2.3.0)
shellany (0.0.1)
shoulda-matchers (4.3.0)
activesupport (>= 4.2.0)
sidekiq (5.2.9)
connection_pool (~> 2.2, >= 2.2.2)
rack (~> 2.0)
Expand Down Expand Up @@ -553,6 +556,7 @@ DEPENDENCIES
jbuilder
jquery-rails
jquery-ui-rails
jwt
kaminari
launchy
letter_opener
Expand All @@ -578,6 +582,7 @@ DEPENDENCIES
rubocop
rubocop-rails (~> 2.8.1)
sass-rails
shoulda-matchers (~> 4.0)
sidekiq
sidekiq-scheduler
simple_form
Expand Down
56 changes: 56 additions & 0 deletions app/controllers/account_requests_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
class AccountRequestsController < ApplicationController
skip_before_action :authorize_user
skip_before_action :authenticate_user!

before_action :set_account_request_from_token, only: [:received, :confirmation, :confirm]

layout 'devise'

def received; end

def confirmation; end

def confirm
@account_request.update!(confirmed_at: Time.current)
AccountRequestMailer.approval_request(account_request_id: @account_request.id).deliver_later
end

def invalid_token; end

def new
@account_request = AccountRequest.new
end

def create
@account_request = AccountRequest.new(account_request_params)

if @account_request.save
AccountRequestMailer.confirmation(account_request_id: @account_request.id).deliver_later

redirect_to received_account_requests_path(token: @account_request.identity_token),
notice: 'Account request was successfully created.'
else
render :new
end
end

private

# Use callbacks to share common setup or constraints between actions.
def set_account_request_from_token
@account_request = AccountRequest.get_by_identity_token(params[:token])

# Use confirmation timestamp instead
if @account_request.nil? || @account_request.confirmed? || @account_request.processed?
redirect_to invalid_token_account_requests_path(token: params[:token])
return
end

@account_request
end

# Only allow a list of trusted parameters through.
def account_request_params
params.require(:account_request).permit(:name, :email, :organization_name, :organization_website, :request_details)
end
end
18 changes: 14 additions & 4 deletions app/controllers/admin/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,22 @@ def index

def new
@organization = Organization.new
@organization.users.build(organization_admin: true)
account_request = params[:token] && AccountRequest.get_by_identity_token(params[:token])

if account_request.blank?
@organization.users.build(organization_admin: true)
elsif account_request.processed?
flash[:error] = "The account request had already been processed and cannot be used again"
@organization.users.build(organization_admin: true)
else
@organization.assign_attributes_from_account_request(account_request)
end
end

def create
@organization = Organization.create(organization_params)
@organization.users.last.update(password: SecureRandom.uuid)
@organization = Organization.new(organization_params)
@organization.users.last.assign_attributes(password: SecureRandom.uuid)

if @organization.save
Organization.seed_items(@organization)
@organization.users.last.invite!
Expand Down Expand Up @@ -63,7 +73,7 @@ def destroy

def organization_params
params.require(:organization)
.permit(:name, :short_name, :street, :city, :state, :zipcode, :email, :url, :logo, :intake_location, :default_email_text,
.permit(:name, :short_name, :street, :city, :state, :zipcode, :email, :url, :logo, :intake_location, :default_email_text, :account_request_id,
users_attributes: %i(name email organization_admin))
end
end
19 changes: 19 additions & 0 deletions app/mailers/account_request_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class AccountRequestMailer < ApplicationMailer
def confirmation(account_request_id:)
@account_request = AccountRequest.find(account_request_id)

mail(
to: @account_request.email,
subject: '[Action Required] Diaperbase Account Request'
)
end

def approval_request(account_request_id:)
@account_request = AccountRequest.find(account_request_id)

mail(
to: 'info@diaper.app',
subject: "[Account Request] #{@account_request.organization_name}"
)
end
end
64 changes: 64 additions & 0 deletions app/models/account_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# == Schema Information
#
# Table name: account_requests
#
# id :bigint not null, primary key
# confirmed_at :datetime
# email :string not null
# name :string not null
# organization_name :string not null
# organization_website :string
# request_details :text not null
# created_at :datetime not null
# updated_at :datetime not null
#
class AccountRequest < ApplicationRecord
validates :name, presence: true
validates :email, presence: true, uniqueness: true
validates :request_details, presence: true, length: { minimum: 50 }
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }

validate :email_not_already_used_by_organization
validate :email_not_already_used_by_user

has_one :organization, dependent: :nullify

def self.get_by_identity_token(identity_token)
decrypted_token = JWT.decode(identity_token, Rails.application.secrets[:secret_key_base], true, { algorithm: 'HS256' })
account_request_id = decrypted_token[0]["account_request_id"]

AccountRequest.find_by(id: account_request_id)
rescue StandardError
# The identity_token was determined to not be valid
# and returns nil to indicate no match found.
nil
end

def identity_token
raise 'must have an id' unless persisted?

JWT.encode({ account_request_id: id }, Rails.application.secrets[:secret_key_base], 'HS256')
end

def confirmed?
confirmed_at.present?
end

def processed?
organization.present?
end

private

def email_not_already_used_by_organization
if Organization.find_by(email: email)
errors.add(:email, 'already used by an existing Organization')
end
end

def email_not_already_used_by_user
if User.find_by(email: email)
errors.add(:email, 'already used by an existing User')
end
end
end
18 changes: 18 additions & 0 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# zipcode :string
# created_at :datetime not null
# updated_at :datetime not null
# account_request_id :integer
#

class Organization < ApplicationRecord
Expand Down Expand Up @@ -113,6 +114,23 @@ def bottom(limit = 5)
scope :alphabetized, -> { order(:name) }
scope :search_name, ->(query) { where('name ilike ?', "%#{query}%") }

def assign_attributes_from_account_request(account_request)
assign_attributes(
name: account_request.organization_name,
url: account_request.organization_website,
email: account_request.email,
account_request_id: account_request.id
)

users.build(
organization_admin: true,
email: account_request.email,
name: account_request.name
)

self
end

# NOTE: when finding Organizations, use Organization.find_by(short_name: params[:organization_id])
def to_param
short_name
Expand Down
27 changes: 27 additions & 0 deletions app/views/account_request_mailer/approval_request.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1> We've just received a confirmed account request from <%= @account_request.organization_name %> </h1>

<h3> Here are their details </h3>
<table>
<thead>
<td>Attribute Name</td>
<td>Value</td>
</thead>

<% @account_request.attributes.each do |ar, val| %>
<tr>
<td><%= ar.humanize %></td>
<td><%= val %></td>
</tr>
<% end %>
</table>

<br>
<%= link_to 'Create This Organization', new_admin_organization_url(token: @account_request.identity_token) %>
</body>
</html>
55 changes: 55 additions & 0 deletions app/views/account_request_mailer/confirmation.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<p>Greetings from the Diaperbase Team,</p>

<h3> Click 'Confirm' when you want to continue requesting an account with us </h3>

<%= link_to 'Confirm This Request', confirmation_account_requests_url(token: @account_request.identity_token) %>

<p>
We're delighted to hear from you and hope you're all staying well!
</p>

<p>
First, and most importantly, DiaperBase is 100% free! We're supported by the non-profit Code for Good and Microsoft sponsors the servers!
</p>

<p>
If you'd like to experience the app, please log in to the sandbox/demo sites and test it out.
Here is the login information for the demo sites:
</p>

<p>
<a href='https://diaperbase.org/'>DiaperBase</a>
<br>
<span>Username: org_admin1@example.com</span>
<br>
<span>Password: password</span>
</p>

<p>
<a href='https://partnerbase.org/'>PartnerBase</a>
<br>
<span>Username: verified@example.com</span>
<br>
<span>Password: password</span>
</p>

<p>
A couple things to know about the sandbox servers before you start using them:
The development team uses the servers for testing our new features and upgrades before putting them on the live site to ensure that no bugs get pushed through to the live site, so if something looks different than the (real) diaper.app site that is probably why! Please don’t enter any sensitive information into the demo servers, several users have access to the demo servers and it will be visible to all users.
</p>

<p>
Finally, we made a getting started video with detailed directions on setting up your diaper bank in DiaperBase, check it out
<a href='https://www.youtube.com/watch?v=fwo3WKMGM_4&feature=youtu.be'>here</a>! Please let us know when you would like to begin using DiaperBase and we will set you up and send you a welcome email.
</p>

<p>Diaperbase Team</p>

</body>
</html>
11 changes: 11 additions & 0 deletions app/views/account_requests/confirm.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="card">
<div class="card-body text-center">
<div class='card-text'>
<h3> Confirmed! <h3>
<h4> We will be processing your request now. </h4>

<p> We will send your invitation via email when we've processed your request. We will reach out to you also via email if we have any questions.</p>
</div>
</div>
</div>

9 changes: 9 additions & 0 deletions app/views/account_requests/confirmation.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="card">
<div class="card-body text-center">
<div class='card-text'>
<h3> Are you ready to get started? </h3>

<%= link_to "I'm ready! Let's go!", confirm_account_requests_path(token: @account_request.identity_token) %></li>
</div>
</div>
</div>
33 changes: 33 additions & 0 deletions app/views/account_requests/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<p id="notice"><%= notice %></p>

<h1>Account Requests</h1>

<table>
<thead>
<tr>
<th>Email</th>
<th>Organization name</th>
<th>Organization website</th>
<th>Request details</th>
<th colspan="3"></th>
</tr>
</thead>

<tbody>
<% @account_requests.each do |account_request| %>
<tr>
<td><%= account_request.email %></td>
<td><%= account_request.organization_name %></td>
<td><%= account_request.organization_website %></td>
<td><%= account_request.request_details %></td>
<td><%= link_to 'Show', account_request %></td>
<td><%= link_to 'Edit', edit_account_request_path(account_request) %></td>
<td><%= link_to 'Destroy', account_request, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>

<br>

<%= link_to 'New Account Request', new_account_request_path %>
Loading

0 comments on commit 2ecdbd0

Please sign in to comment.