Use a state machine for user status
The user status is a bit complex, since there are various states and not all transitions between them make sense. Using AASM means that we can name and restrict the transitions, which hopefully makes them easier to reason about.
This commit is contained in:
parent
786f28993a
commit
1a11c4dc19
12 changed files with 103 additions and 45 deletions
|
@ -25,7 +25,7 @@ class ConfirmationsController < ApplicationController
|
|||
render_unknown_user token.user.display_name
|
||||
else
|
||||
user = token.user
|
||||
user.status = "active"
|
||||
user.activate
|
||||
user.email_valid = true
|
||||
flash[:notice] = gravatar_status_message(user) if gravatar_enable(user)
|
||||
user.save!
|
||||
|
|
|
@ -46,7 +46,7 @@ class PasswordsController < ApplicationController
|
|||
if params[:user]
|
||||
current_user.pass_crypt = params[:user][:pass_crypt]
|
||||
current_user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
|
||||
current_user.status = "active" if current_user.status == "pending"
|
||||
current_user.activate if current_user.may_activate?
|
||||
current_user.email_valid = true
|
||||
|
||||
if current_user.save
|
||||
|
|
|
@ -164,8 +164,6 @@ class UsersController < ApplicationController
|
|||
|
||||
Rails.logger.info "create: #{session[:referer]}"
|
||||
|
||||
current_user.status = "pending"
|
||||
|
||||
if current_user.auth_provider.present? && current_user.pass_crypt.empty?
|
||||
# We are creating an account with external authentication and
|
||||
# no password was specified so create a random one
|
||||
|
@ -202,15 +200,17 @@ class UsersController < ApplicationController
|
|||
##
|
||||
# sets a user's status
|
||||
def set_status
|
||||
@user.status = params[:status]
|
||||
@user.save
|
||||
@user.activate! if params[:event] == "activate"
|
||||
@user.confirm! if params[:event] == "confirm"
|
||||
@user.hide! if params[:event] == "hide"
|
||||
@user.unhide! if params[:event] == "unhide"
|
||||
redirect_to user_path(:display_name => params[:display_name])
|
||||
end
|
||||
|
||||
##
|
||||
# destroy a user, marking them as deleted and removing personal data
|
||||
def destroy
|
||||
@user.destroy
|
||||
@user.soft_destroy!
|
||||
redirect_to user_path(:display_name => params[:display_name])
|
||||
end
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
|
||||
class User < ApplicationRecord
|
||||
require "digest"
|
||||
include AASM
|
||||
|
||||
has_many :traces, -> { where(:visible => true) }
|
||||
has_many :diary_entries, -> { order(:created_at => :desc) }
|
||||
|
@ -158,6 +159,51 @@ class User < ApplicationRecord
|
|||
user
|
||||
end
|
||||
|
||||
aasm :column => :status, :no_direct_assignment => true do
|
||||
state :pending, :initial => true
|
||||
state :active
|
||||
state :confirmed
|
||||
state :suspended
|
||||
state :deleted
|
||||
|
||||
# A normal account is active
|
||||
event :activate do
|
||||
transitions :from => :pending, :to => :active
|
||||
end
|
||||
|
||||
# Used in test suite, not something that we would normally need to do.
|
||||
event :deactivate do
|
||||
transitions :from => :active, :to => :pending
|
||||
end
|
||||
|
||||
# To confirm an account is used to override the spam scoring
|
||||
event :confirm do
|
||||
transitions :from => [:pending, :active, :suspended], :to => :confirmed
|
||||
end
|
||||
|
||||
event :suspend do
|
||||
transitions :from => [:pending, :active], :to => :suspended
|
||||
end
|
||||
|
||||
# Mark the account as deleted but keep all data intact
|
||||
event :hide do
|
||||
transitions :from => [:pending, :active, :confirmed, :suspended], :to => :deleted
|
||||
end
|
||||
|
||||
event :unhide do
|
||||
transitions :from => [:deleted], :to => :active
|
||||
end
|
||||
|
||||
# Mark the account as deleted and remove personal data
|
||||
event :soft_destroy do
|
||||
before do
|
||||
remove_personal_data
|
||||
end
|
||||
|
||||
transitions :from => [:pending, :active, :confirmed, :suspended], :to => :deleted
|
||||
end
|
||||
end
|
||||
|
||||
def description
|
||||
RichText.new(self[:description_format], self[:description])
|
||||
end
|
||||
|
@ -241,8 +287,8 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
##
|
||||
# destroy a user - leave the account but purge most personal data
|
||||
def destroy
|
||||
# remove personal data - leave the account but purge most personal data
|
||||
def remove_personal_data
|
||||
avatar.purge_later
|
||||
|
||||
self.display_name = "user_#{id}"
|
||||
|
@ -253,7 +299,6 @@ class User < ApplicationRecord
|
|||
self.new_email = nil
|
||||
self.auth_provider = nil
|
||||
self.auth_uid = nil
|
||||
self.status = "deleted"
|
||||
|
||||
save
|
||||
end
|
||||
|
@ -279,7 +324,7 @@ class User < ApplicationRecord
|
|||
##
|
||||
# perform a spam check on a user
|
||||
def spam_check
|
||||
update(:status => "suspended") if status == "active" && spam_score > Settings.spam_threshold
|
||||
suspend! if may_suspend? && spam_score > Settings.spam_threshold
|
||||
end
|
||||
|
||||
##
|
||||
|
|
|
@ -139,30 +139,32 @@
|
|||
<nav class='secondary-actions'>
|
||||
<ul class='clearfix'>
|
||||
<% if can? :set_status, User %>
|
||||
<% if ["active", "confirmed"].include? @user.status %>
|
||||
<% if @user.may_activate? %>
|
||||
<li>
|
||||
<%= link_to t(".deactivate_user"), set_status_user_path(:status => "pending", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
</li>
|
||||
<% elsif ["pending"].include? @user.status %>
|
||||
<li>
|
||||
<%= link_to t(".activate_user"), set_status_user_path(:status => "active", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
<%= link_to t(".activate_user"), set_status_user_path(:event => "activate", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if ["active", "suspended"].include? @user.status %>
|
||||
<% if @user.may_confirm? %>
|
||||
<li>
|
||||
<%= link_to t(".confirm_user"), set_status_user_path(:status => "confirmed", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
<%= link_to t(".confirm_user"), set_status_user_path(:event => "confirm", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if @user.may_hide? %>
|
||||
<li>
|
||||
<%= link_to t(".hide_user"), set_status_user_path(:event => "hide", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if @user.may_unhide? %>
|
||||
<li>
|
||||
<%= link_to t(".unhide_user"), set_status_user_path(:event => "unhide", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
</li>
|
||||
<% end %>
|
||||
<li>
|
||||
<% if ["pending", "active", "confirmed", "suspended"].include? @user.status %>
|
||||
<%= link_to t(".hide_user"), set_status_user_path(:status => "deleted", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
<% else %>
|
||||
<%= link_to t(".unhide_user"), set_status_user_path(:status => "active", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if can? :destroy, User %>
|
||||
|
||||
<% if can?(:destroy, User) && @user.may_soft_destroy? %>
|
||||
<li>
|
||||
<%= link_to t(".delete_user"), user_path(:display_name => @user.display_name), :method => :delete, :data => { :confirm => t(".confirm") } %>
|
||||
</li>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue