From: Andy Allan Date: Wed, 5 Jan 2022 18:44:46 +0000 (+0000) Subject: Use a state machine for user status X-Git-Tag: live~1257^2~2 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/1a11c4dc191d93b18fcf5aa917448c8cd6d2556b 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. --- diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index bcb4c1617..e54fa4a5d 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -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! diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 331575964..502b1357f 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -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 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f7a82c08c..ea04e14f6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 8c75b4ef4..123ef3230 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 ## diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 95878402b..b3502f29e 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -139,30 +139,32 @@