bullet_train 1.0.4 → 1.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/account/invitations_controller.rb +1 -144
- data/app/controllers/account/memberships_controller.rb +1 -130
- data/app/controllers/account/onboarding/user_details_controller.rb +1 -61
- data/app/controllers/account/onboarding/user_email_controller.rb +1 -63
- data/app/controllers/account/teams_controller.rb +1 -120
- data/app/controllers/account/two_factors_controller.rb +18 -0
- data/app/controllers/account/users_controller.rb +1 -57
- data/app/controllers/concerns/account/controllers/base.rb +118 -0
- data/app/controllers/concerns/account/invitations/controller_base.rb +150 -0
- data/app/controllers/concerns/account/memberships/controller_base.rb +136 -0
- data/app/controllers/concerns/account/onboarding/user_details/controller_base.rb +67 -0
- data/app/controllers/concerns/account/onboarding/user_email/controller_base.rb +69 -0
- data/app/controllers/concerns/account/teams/controller_base.rb +126 -0
- data/app/controllers/concerns/account/users/controller_base.rb +63 -0
- data/app/controllers/concerns/controllers/base.rb +119 -0
- data/app/controllers/concerns/devise_current_attributes.rb +13 -0
- data/app/controllers/concerns/registrations/controller_base.rb +39 -0
- data/app/controllers/concerns/sessions/controller_base.rb +15 -0
- data/app/controllers/registrations_controller.rb +9 -0
- data/app/controllers/sessions_controller.rb +3 -0
- data/app/helpers/account/buttons_helper.rb +12 -0
- data/app/helpers/account/dates_helper.rb +38 -0
- data/app/helpers/account/forms_helper.rb +67 -0
- data/app/helpers/account/locale_helper.rb +51 -0
- data/app/helpers/account/markdown_helper.rb +5 -0
- data/app/helpers/account/role_helper.rb +5 -0
- data/app/helpers/attributes_helper.rb +32 -0
- data/app/helpers/email_helper.rb +7 -0
- data/app/helpers/images_helper.rb +7 -0
- data/app/mailers/concerns/mailers/base.rb +16 -0
- data/app/mailers/devise_mailer.rb +10 -0
- data/app/mailers/user_mailer.rb +24 -0
- data/app/models/concerns/invitations/{core.rb → base.rb} +1 -1
- data/app/models/concerns/memberships/{core.rb → base.rb} +1 -1
- data/app/models/concerns/teams/{core.rb → base.rb} +1 -1
- data/app/models/concerns/users/{core.rb → base.rb} +1 -1
- data/app/views/account/two_factors/create.js.erb +1 -0
- data/app/views/account/two_factors/destroy.js.erb +1 -0
- data/app/views/devise/confirmations/new.html.erb +16 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +30 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/passwords/edit.html.erb +17 -0
- data/app/views/devise/passwords/new.html.erb +20 -0
- data/app/views/devise/registrations/_two_factor.html.erb +42 -0
- data/app/views/devise/registrations/edit.html.erb +43 -0
- data/app/views/devise/registrations/new.html.erb +30 -0
- data/app/views/devise/sessions/new.html.erb +59 -0
- data/app/views/devise/sessions/pre_otp.js.erb +11 -0
- data/app/views/devise/shared/_links.html.erb +21 -0
- data/app/views/devise/shared/_oauth.html.erb +9 -0
- data/app/views/devise/unlocks/new.html.erb +16 -0
- data/app/views/layouts/account.html.erb +1 -0
- data/app/views/layouts/devise.html.erb +1 -0
- data/config/locales/en/devise.en.yml +110 -0
- data/lib/bullet_train/version.rb +1 -1
- metadata +52 -6
@@ -0,0 +1,118 @@
|
|
1
|
+
module Account::Controllers::Base
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
include LoadsAndAuthorizesResource
|
6
|
+
include Fields::ControllerSupport
|
7
|
+
|
8
|
+
before_action :set_last_seen_at, if: proc {
|
9
|
+
user_signed_in? && (current_user.last_seen_at.nil? || current_user.last_seen_at < 1.minute.ago)
|
10
|
+
}
|
11
|
+
|
12
|
+
layout "account"
|
13
|
+
|
14
|
+
before_action :authenticate_user!
|
15
|
+
before_action :set_paper_trail_whodunnit
|
16
|
+
before_action :ensure_onboarding_is_complete_and_set_next_step
|
17
|
+
end
|
18
|
+
|
19
|
+
class_methods do
|
20
|
+
# this is a template method called by LoadsAndAuthorizesResource.
|
21
|
+
# it allows that module to understand what namespaces of a controller
|
22
|
+
# are organizing the controllers, and which are organizing the models.
|
23
|
+
def regex_to_remove_controller_namespace
|
24
|
+
/^Account::/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def adding_user_email?
|
29
|
+
is_a?(Account::Onboarding::UserEmailController)
|
30
|
+
end
|
31
|
+
|
32
|
+
def adding_user_details?
|
33
|
+
is_a?(Account::Onboarding::UserDetailsController)
|
34
|
+
end
|
35
|
+
|
36
|
+
def adding_team?
|
37
|
+
return true if request.get? && request.path == new_account_team_path
|
38
|
+
return true if request.post? && request.path == account_teams_path
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def switching_teams?
|
43
|
+
return true if request.get? && request.path == account_teams_path
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def managing_account?
|
48
|
+
is_a?(Account::UsersController) || self.class.module_parents.include?(Oauth)
|
49
|
+
end
|
50
|
+
|
51
|
+
def accepting_invitation?
|
52
|
+
(params[:controller] == "account/invitations") && (params[:action] == "show" || params[:action] == "accept")
|
53
|
+
end
|
54
|
+
|
55
|
+
def ensure_onboarding_is_complete_and_set_next_step
|
56
|
+
unless ensure_onboarding_is_complete
|
57
|
+
session[:after_onboarding_url] ||= request.url
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ensure_onboarding_is_complete
|
62
|
+
# This is temporary, but if we've gotten this far and `@team` is loaded, we need to ensure current_team is
|
63
|
+
# updated for the checks below. This entire concept of `current_team` is going away soon.
|
64
|
+
current_user.update(current_team: @team) if @team&.persisted?
|
65
|
+
|
66
|
+
# since the onboarding controllers are child classes of this class,
|
67
|
+
# we actually have to check to make sure we're not currently on that
|
68
|
+
# step otherwise we'll end up in an endless redirection loop.
|
69
|
+
if current_user.email_is_oauth_placeholder?
|
70
|
+
if adding_user_email?
|
71
|
+
return true
|
72
|
+
else
|
73
|
+
redirect_to edit_account_onboarding_user_email_path(current_user)
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# some team-related onboarding steps need to be skipped if we're in the process
|
79
|
+
# of creating a new team.
|
80
|
+
unless adding_team? || accepting_invitation?
|
81
|
+
|
82
|
+
# USER ONBOARDING STUFF
|
83
|
+
# first we make sure the user is properly onboarded.
|
84
|
+
unless current_team.present?
|
85
|
+
|
86
|
+
# don't force the user to create a team if they've already got one.
|
87
|
+
if current_user.teams.any?
|
88
|
+
current_user.update(current_team: current_user.teams.first)
|
89
|
+
else
|
90
|
+
redirect_to new_account_team_path
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# TEAM ONBOARDING STUFF.
|
96
|
+
# then make sure the team is properly onboarded.
|
97
|
+
|
98
|
+
# since the onboarding controllers are child classes of this class,
|
99
|
+
# we actually have to check to make sure we're not currently on that
|
100
|
+
# step otherwise we'll end up in an endless redirection loop.
|
101
|
+
unless current_user.details_provided?
|
102
|
+
if adding_user_details?
|
103
|
+
return true
|
104
|
+
else
|
105
|
+
redirect_to edit_account_onboarding_user_detail_path(current_user)
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def set_last_seen_at
|
116
|
+
current_user.update_attribute(:last_seen_at, Time.current)
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Account::Invitations::ControllerBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
# the 'accept' action isn't covered by cancancan, because simply having the
|
6
|
+
# link is authorization enough to claim membership on a team. see `#accept`
|
7
|
+
# for more information.
|
8
|
+
account_load_and_authorize_resource :invitation, :team, except: [:show]
|
9
|
+
|
10
|
+
# we skip the onboarding requirement for users claiming and invitation.
|
11
|
+
# this way the invitation gets accepted after they complete the devise
|
12
|
+
# workflow, but before they're prompted to complete their onboarding.
|
13
|
+
skip_before_action :ensure_onboarding_is_complete_and_set_next_step, only: :accept
|
14
|
+
skip_before_action :authenticate_user!, only: :accept
|
15
|
+
end
|
16
|
+
|
17
|
+
# GET /invitations
|
18
|
+
# GET /invitations.json
|
19
|
+
def index
|
20
|
+
redirect_to [:account, @team, :memberships]
|
21
|
+
end
|
22
|
+
|
23
|
+
# GET /invitations/1
|
24
|
+
# GET /invitations/1.json
|
25
|
+
def show
|
26
|
+
# it's important that we only allow invitations to be shown via their uuid,
|
27
|
+
# otherwise team members can just step the id in the url to claim an
|
28
|
+
# invitation that would escalate their privileges.
|
29
|
+
@invitation = Invitation.find_by(uuid: params[:id])
|
30
|
+
unless @invitation
|
31
|
+
raise I18n.t("global.notifications.not_found")
|
32
|
+
end
|
33
|
+
@team = @invitation.team
|
34
|
+
|
35
|
+
# backfill these objects for the locale magic, since we didn't use `account_load_and_authorize_resource`.
|
36
|
+
@child_object = @invitation
|
37
|
+
@parent_object = @team
|
38
|
+
|
39
|
+
render layout: "devise"
|
40
|
+
end
|
41
|
+
|
42
|
+
# POST /invitations/1/accept
|
43
|
+
# POST /invitations/1/accept.json
|
44
|
+
def accept
|
45
|
+
# unless the user is signed in.
|
46
|
+
if !current_user.present?
|
47
|
+
|
48
|
+
# keep track of the uuid of the invitation so we can reload it
|
49
|
+
# after they sign up. at this point we don't even know if it's
|
50
|
+
# valid, but that's fine.
|
51
|
+
session[:invitation_uuid] = params[:id]
|
52
|
+
|
53
|
+
# also, we'll queue devise up to return to the invitation url after a sign in.
|
54
|
+
session["user_return_to"] = request.path
|
55
|
+
|
56
|
+
# assume the user needs to create an account.
|
57
|
+
# this is not the default for devise, but a sensible default here.
|
58
|
+
redirect_to new_user_registration_path
|
59
|
+
|
60
|
+
else
|
61
|
+
|
62
|
+
@invitation = Invitation.find_by(uuid: params[:id])
|
63
|
+
|
64
|
+
if @invitation
|
65
|
+
@team = @invitation.team
|
66
|
+
if @invitation.is_for?(current_user) || request.post?
|
67
|
+
@invitation.accept_for(current_user)
|
68
|
+
redirect_to account_dashboard_path, notice: I18n.t("invitations.notifications.welcome", team_name: @team.name)
|
69
|
+
else
|
70
|
+
redirect_to account_invitation_path(@invitation.uuid)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
redirect_to account_dashboard_path
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# GET /invitations/new
|
80
|
+
def new
|
81
|
+
@invitation.build_membership
|
82
|
+
@cancel_path = only_allow_path(params[:cancel_path])
|
83
|
+
end
|
84
|
+
|
85
|
+
# POST /invitations
|
86
|
+
# POST /invitations.json
|
87
|
+
def create
|
88
|
+
@invitation.membership.team = current_team
|
89
|
+
# this allows notifications to be sent to a user before they've accepted their invitation.
|
90
|
+
@invitation.membership.user_email = @invitation.email
|
91
|
+
@invitation.from_membership = current_membership
|
92
|
+
respond_to do |format|
|
93
|
+
if @invitation.save
|
94
|
+
format.html { redirect_to account_team_invitations_path(@team), notice: I18n.t("invitations.notifications.created") }
|
95
|
+
format.json { render :show, status: :created, location: [:account, @team, @invitation] }
|
96
|
+
else
|
97
|
+
format.html { render :new, status: :unprocessable_entity }
|
98
|
+
format.json { render json: @invitation.errors, status: :unprocessable_entity }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# DELETE /invitations/1
|
104
|
+
# DELETE /invitations/1.json
|
105
|
+
def destroy
|
106
|
+
@invitation.destroy
|
107
|
+
respond_to do |format|
|
108
|
+
format.html { redirect_to account_team_invitations_path(@team), notice: I18n.t("invitations.notifications.destroyed") }
|
109
|
+
format.json { head :no_content }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def manageable_role_keys
|
116
|
+
helpers.current_membership.manageable_roles.map(&:key)
|
117
|
+
end
|
118
|
+
|
119
|
+
# NOTE this method is only designed to work in the context of creating a invitation.
|
120
|
+
# we don't provide any support for updating invitations.
|
121
|
+
def invitation_params
|
122
|
+
# we use strong params first.
|
123
|
+
strong_params = params.require(:invitation).permit(
|
124
|
+
:email,
|
125
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
126
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
127
|
+
membership_attributes: [
|
128
|
+
:user_first_name,
|
129
|
+
:user_last_name,
|
130
|
+
role_ids: []
|
131
|
+
],
|
132
|
+
)
|
133
|
+
|
134
|
+
# after that, we have to be more careful how we assign the roles.
|
135
|
+
# we can't let users assign roles to an invitation that they don't have permission
|
136
|
+
# to assign, but they do have permission to assign some to other team members.
|
137
|
+
if params[:invitation] && params[:invitation][:role_ids].present?
|
138
|
+
|
139
|
+
# ensure the list of role keys from the form only includes keys that they're allowed to assign.
|
140
|
+
assignable_role_keys_from_the_form = params[:invitation][:role_ids].map(&:to_i) & manageable_role_keys
|
141
|
+
|
142
|
+
strong_params[:role_ids] = assignable_role_keys_from_the_form
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
147
|
+
|
148
|
+
strong_params
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Account::Memberships::ControllerBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
account_load_and_authorize_resource :membership, :team, member_actions: [:demote, :promote, :reinvite], collection_actions: [:search]
|
6
|
+
end
|
7
|
+
|
8
|
+
def index
|
9
|
+
unless @memberships.count > 0
|
10
|
+
redirect_to account_team_invitations_path(@team), notice: I18n.t("memberships.notifications.no_members")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def search
|
15
|
+
# TODO This is a particularly crazy example where we're doing the search logic ourselves in SQL.
|
16
|
+
# In the future, I could see us replacing this with a recommended example using Elasticsearch and the `searchkick` Ruby Gem.
|
17
|
+
limit = params[:limit] || 100
|
18
|
+
page = [params[:page].to_i, 1].max # Ensure we never have a negative or zero page value
|
19
|
+
search_term = "%#{params[:search]&.upcase}%"
|
20
|
+
offset = (page - 1) * limit
|
21
|
+
# Currently we're only searching on user.first_name, user.last_name, memberships.user_first_name and memberships.user_last_name. Should we also search on the email address?
|
22
|
+
# This query could use impromement. Currently if you search for "Ad Pal" you wouldn't find a user "Adam Pallozzi"
|
23
|
+
query = "UPPER(first_name) LIKE :search_term OR UPPER(last_name) LIKE :search_term OR UPPER(user_first_name) LIKE :search_term OR UPPER(user_last_name) LIKE :search_term"
|
24
|
+
# We're using left outer join here because we may get memberships that don't belong to a membership yet
|
25
|
+
memberships = @team.memberships.accessible_by(current_ability, :show).left_outer_joins(:user).where(query, search_term: search_term)
|
26
|
+
total_results = memberships.size
|
27
|
+
# the Areal.sql(LOWER(COALESCE...)) part means that if the user record doesn't exist or if there are records that start with a lower case letter, we will still sort everything correctly using the user.first_name instead.
|
28
|
+
memberships_array = memberships.limit(limit).offset(offset).order(Arel.sql("LOWER(COALESCE(first_name, user_first_name) )")).map { |membership| {id: membership.id, text: membership.label_string.to_s} }
|
29
|
+
results = {results: memberships_array, pagination: {more: (total_results > page * limit)}}
|
30
|
+
render json: results.to_json
|
31
|
+
end
|
32
|
+
|
33
|
+
def show
|
34
|
+
end
|
35
|
+
|
36
|
+
def edit
|
37
|
+
end
|
38
|
+
|
39
|
+
# PATCH/PUT /account/memberships/:id
|
40
|
+
# PATCH/PUT /account/memberships/:id.json
|
41
|
+
def update
|
42
|
+
respond_to do |format|
|
43
|
+
if @membership.update(membership_params)
|
44
|
+
format.html { redirect_to [:account, @membership], notice: I18n.t("memberships.notifications.updated") }
|
45
|
+
format.json { render :show, status: :ok, location: [:account, @membership] }
|
46
|
+
else
|
47
|
+
format.html { render :edit, status: :unprocessable_entity }
|
48
|
+
format.json { render json: @membership.errors, status: :unprocessable_entity }
|
49
|
+
end
|
50
|
+
rescue RemovingLastTeamAdminException => _
|
51
|
+
format.html { redirect_to [:account, @team, :memberships], alert: I18n.t("memberships.notifications.cant_demote") }
|
52
|
+
format.json { render json: {exception: I18n.t("memberships.notifications.cant_demote")}, status: :unprocessable_entity }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def demote
|
57
|
+
@membership.roles.delete Role.admin
|
58
|
+
redirect_to account_team_memberships_path(@team)
|
59
|
+
rescue RemovingLastTeamAdminException => _
|
60
|
+
redirect_to account_team_memberships_path(@team), alert: I18n.t("memberships.notifications.cant_demote")
|
61
|
+
end
|
62
|
+
|
63
|
+
def promote
|
64
|
+
@membership.roles << Role.admin unless @membership.roles.include?(Role.admin)
|
65
|
+
redirect_to account_team_memberships_path(@team)
|
66
|
+
end
|
67
|
+
|
68
|
+
def destroy
|
69
|
+
# Instead of destroying the membership, we nullify the user_id and use the membership record as a 'Tombstone' for referencing past associations (eg message at-mentions and Scaffolding::CompletelyConcrete::TangibleThings::Assignment)
|
70
|
+
|
71
|
+
user_was = @membership.user
|
72
|
+
@membership.nullify_user
|
73
|
+
|
74
|
+
if user_was == current_user
|
75
|
+
# if a user removes themselves from a team, we'll have to send them to their dashboard.
|
76
|
+
redirect_to account_dashboard_path, notice: I18n.t("memberships.notifications.you_removed_yourself", team_name: @team.name)
|
77
|
+
else
|
78
|
+
redirect_to [:account, @team, :memberships], notice: I18n.t("memberships.notifications.destroyed")
|
79
|
+
end
|
80
|
+
rescue RemovingLastTeamAdminException
|
81
|
+
redirect_to account_team_memberships_path(@team), alert: I18n.t("memberships.notifications.cant_remove")
|
82
|
+
end
|
83
|
+
|
84
|
+
def reinvite
|
85
|
+
@invitation = Invitation.new(membership: @membership, team: @team, email: @membership.user_email, from_membership: current_membership)
|
86
|
+
if @invitation.save
|
87
|
+
redirect_to [:account, @team, :memberships], notice: I18n.t("account.memberships.notifications.reinvited")
|
88
|
+
else
|
89
|
+
redirect_to [:account, @team, :memberships], notice: "There was an error creating the invitation (#{@invitation.errors.full_messages.to_sentence})"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def manageable_role_keys
|
96
|
+
helpers.current_membership.manageable_roles.map(&:key)
|
97
|
+
end
|
98
|
+
|
99
|
+
# NOTE this method is only designed to work in the context of updating a membership.
|
100
|
+
# we don't provide any support for creating memberships other than by an invitation.
|
101
|
+
def membership_params
|
102
|
+
# we use strong params first.
|
103
|
+
strong_params = params.require(:membership).permit(
|
104
|
+
:user_first_name,
|
105
|
+
:user_last_name,
|
106
|
+
:user_profile_photo_id,
|
107
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
108
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
109
|
+
)
|
110
|
+
|
111
|
+
# after that, we have to be more careful how we update the roles.
|
112
|
+
# we can't let users remove roles from a membership that they don't have permission
|
113
|
+
# to remove, but we want to allow them to add or remove other roles they do have
|
114
|
+
# permission to assign to other team members.
|
115
|
+
if params[:membership] && params[:membership][:role_ids].present?
|
116
|
+
|
117
|
+
# first, start with the list of role keys already assigned to this membership.
|
118
|
+
existing_role_keys = @membership.role_ids
|
119
|
+
|
120
|
+
# generate a list of role keys we can't allow the current user to remove from this membership.
|
121
|
+
existing_role_keys_that_are_unmanageable = existing_role_keys - manageable_role_keys
|
122
|
+
|
123
|
+
# now let's ensure the list of role keys from the form only includes keys that they're allowed to assign.
|
124
|
+
assignable_role_keys_from_the_form = params[:membership][:role_ids].map(&:to_s) & manageable_role_keys
|
125
|
+
|
126
|
+
# any role keys that are manageable by the current user have to then come from the form data,
|
127
|
+
# otherwise we can assume they were removed by being unchecked.
|
128
|
+
strong_params[:role_ids] = existing_role_keys_that_are_unmanageable + assignable_role_keys_from_the_form
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
133
|
+
|
134
|
+
strong_params
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Account::Onboarding::UserDetails::ControllerBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
layout "devise"
|
6
|
+
load_and_authorize_resource class: "User"
|
7
|
+
|
8
|
+
# this is because cancancan doesn't let us set the instance variable name above.
|
9
|
+
before_action do
|
10
|
+
@user = @user_detail
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# GET /users/1/edit
|
15
|
+
def edit
|
16
|
+
flash[:notice] = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# PATCH/PUT /users/1
|
20
|
+
# PATCH/PUT /users/1.json
|
21
|
+
def update
|
22
|
+
respond_to do |format|
|
23
|
+
if @user.update(user_params)
|
24
|
+
# if you update your own user account, devise will normally kick you out, so we do this instead.
|
25
|
+
bypass_sign_in current_user.reload
|
26
|
+
|
27
|
+
if @user.details_provided?
|
28
|
+
format.html { redirect_to account_team_path(@user.teams.first), notice: "" }
|
29
|
+
else
|
30
|
+
format.html {
|
31
|
+
flash[:error] = I18n.t("global.notifications.all_fields_required")
|
32
|
+
redirect_to edit_account_onboarding_user_detail_path(@user)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
format.json { render :show, status: :ok, location: [:account, @user] }
|
37
|
+
else
|
38
|
+
format.html { render :edit, status: :unprocessable_entity }
|
39
|
+
format.json { render json: @user.errors, status: :unprocessable_entity }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Never trust parameters from the scary internet, only allow the white list through.
|
47
|
+
def user_params
|
48
|
+
permitted_attributes = [
|
49
|
+
:first_name,
|
50
|
+
:last_name,
|
51
|
+
:time_zone,
|
52
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
53
|
+
]
|
54
|
+
|
55
|
+
permitted_hash = {
|
56
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
57
|
+
}
|
58
|
+
|
59
|
+
if can? :edit, @user.current_team
|
60
|
+
permitted_hash[:current_team_attributes] = [:id, :name]
|
61
|
+
end
|
62
|
+
|
63
|
+
params.require(:user).permit(permitted_attributes, permitted_hash)
|
64
|
+
|
65
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Account::Onboarding::UserEmail::ControllerBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
layout "devise"
|
6
|
+
load_and_authorize_resource class: "User"
|
7
|
+
|
8
|
+
# this is because cancancan doesn't let us set the instance variable name above.
|
9
|
+
before_action do
|
10
|
+
@user = @user_email
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# GET /users/1/edit
|
15
|
+
def edit
|
16
|
+
flash[:notice] = nil
|
17
|
+
if @user.email_is_oauth_placeholder?
|
18
|
+
@user.email = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# PATCH/PUT /users/1
|
23
|
+
# PATCH/PUT /users/1.json
|
24
|
+
def update
|
25
|
+
respond_to do |format|
|
26
|
+
if @user.update(user_params)
|
27
|
+
# if you update your own user account, devise will normally kick you out, so we do this instead.
|
28
|
+
bypass_sign_in current_user.reload
|
29
|
+
|
30
|
+
if !@user.email_is_oauth_placeholder?
|
31
|
+
@user.send_welcome_email
|
32
|
+
format.html { redirect_to account_team_path(@user.teams.first), notice: "" }
|
33
|
+
else
|
34
|
+
format.html {
|
35
|
+
flash[:error] = I18n.t("global.notifications.all_fields_required")
|
36
|
+
redirect_to edit_account_onboarding_user_detail_path(@user)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
format.json { render :show, status: :ok, location: [:account, @user] }
|
41
|
+
else
|
42
|
+
|
43
|
+
# this is just checking whether the error on the email field is taking the email
|
44
|
+
# address is already taken.
|
45
|
+
@email_taken = begin
|
46
|
+
@user.errors.details[:email].select { |error| error[:error] == :taken }.any?
|
47
|
+
rescue
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
format.html { render :edit, status: :unprocessable_entity }
|
52
|
+
format.json { render json: @user.errors, status: :unprocessable_entity }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Never trust parameters from the scary internet, only allow the white list through.
|
60
|
+
def user_params
|
61
|
+
params.require(:user).permit(
|
62
|
+
:email,
|
63
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
64
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
65
|
+
)
|
66
|
+
|
67
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Account::Teams::ControllerBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
load_and_authorize_resource :team, class: "Team", prepend: true
|
6
|
+
|
7
|
+
prepend_before_action do
|
8
|
+
if params["action"] == "new"
|
9
|
+
current_user.current_team = nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
before_action :enforce_invitation_only, only: [:create]
|
14
|
+
|
15
|
+
before_action do
|
16
|
+
# for magic locales.
|
17
|
+
@child_object = @team
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# GET /teams
|
22
|
+
# GET /teams.json
|
23
|
+
def index
|
24
|
+
# if a user doesn't have multiple teams, we try to simplify the team ui/ux
|
25
|
+
# as much as possible. links to this page should go to the current team
|
26
|
+
# dashboard. however, some other links to this page are actually in branch
|
27
|
+
# logic and will not display at all. instead, users will be linked to the
|
28
|
+
# "new team" page. (see the main account sidebar menu for an example of
|
29
|
+
# this.)
|
30
|
+
unless current_user.multiple_teams?
|
31
|
+
redirect_to account_team_path(current_team)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# POST /teams/1/switch
|
36
|
+
def switch_to
|
37
|
+
current_user.current_team = @team
|
38
|
+
current_user.save
|
39
|
+
redirect_to account_dashboard_path
|
40
|
+
end
|
41
|
+
|
42
|
+
# GET /teams/1
|
43
|
+
# GET /teams/1.json
|
44
|
+
def show
|
45
|
+
# I don't think this is the best place to close the loop on the onboarding process, but practically speaking it's
|
46
|
+
# the easiest place to implement this at the moment, because all the onboarding steps redirect here on success.
|
47
|
+
if session[:after_onboarding_url].present?
|
48
|
+
redirect_to session.delete(:after_onboarding_url)
|
49
|
+
end
|
50
|
+
|
51
|
+
current_user.current_team = @team
|
52
|
+
current_user.save
|
53
|
+
end
|
54
|
+
|
55
|
+
# GET /teams/new
|
56
|
+
def new
|
57
|
+
render :new, layout: "devise"
|
58
|
+
end
|
59
|
+
|
60
|
+
# GET /teams/1/edit
|
61
|
+
def edit
|
62
|
+
end
|
63
|
+
|
64
|
+
# POST /teams
|
65
|
+
# POST /teams.json
|
66
|
+
def create
|
67
|
+
@team = Team.new(team_params)
|
68
|
+
|
69
|
+
respond_to do |format|
|
70
|
+
if @team.save
|
71
|
+
|
72
|
+
# also make the creator of the team the default admin.
|
73
|
+
@team.memberships.create(user: current_user, roles: [Role.admin])
|
74
|
+
|
75
|
+
current_user.current_team = @team
|
76
|
+
current_user.former_user = false
|
77
|
+
current_user.save
|
78
|
+
|
79
|
+
format.html { redirect_to [:account, @team], notice: I18n.t("teams.notifications.created") }
|
80
|
+
format.json { render :show, status: :created, location: [:account, @team] }
|
81
|
+
else
|
82
|
+
format.html { render :new, layout: "devise" }
|
83
|
+
format.json { render json: @team.errors, status: :unprocessable_entity }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# PATCH/PUT /teams/1
|
89
|
+
# PATCH/PUT /teams/1.json
|
90
|
+
def update
|
91
|
+
respond_to do |format|
|
92
|
+
if @team.update(team_params)
|
93
|
+
format.html { redirect_to [:account, @team], notice: I18n.t("teams.notifications.updated") }
|
94
|
+
format.json { render :show, status: :ok, location: [:account, @team] }
|
95
|
+
else
|
96
|
+
format.html { render :edit, status: :unprocessable_entity }
|
97
|
+
format.json { render json: @team.errors, status: :unprocessable_entity }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# # DELETE /teams/1
|
103
|
+
# # DELETE /teams/1.json
|
104
|
+
# def destroy
|
105
|
+
# @team.destroy
|
106
|
+
# respond_to do |format|
|
107
|
+
# format.html { redirect_to account_teams_url, notice: 'Team was successfully destroyed.' }
|
108
|
+
# format.json { head :no_content }
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# Never trust parameters from the scary internet, only allow the white list through.
|
115
|
+
def team_params
|
116
|
+
params.require(:team).permit(
|
117
|
+
:name,
|
118
|
+
:time_zone,
|
119
|
+
:locale,
|
120
|
+
# 🚅 super scaffolding will insert new fields above this line.
|
121
|
+
# 🚅 super scaffolding will insert new arrays above this line.
|
122
|
+
)
|
123
|
+
|
124
|
+
# 🚅 super scaffolding will insert processing for new fields above this line.
|
125
|
+
end
|
126
|
+
end
|