app/models/lato/user.rb



module Lato
  class User < ApplicationRecord
    include LatoUserApplication

    has_secure_password

    # Validations
    ##

    validates :first_name, presence: true
    validates :last_name, presence: true
    validates :email, presence: true, uniqueness: true
    validates :accepted_privacy_policy_version, presence: true
    validates :accepted_terms_and_conditions_version, presence: true

    # Relations
    ##

    has_many :lato_operations, class_name: 'Lato::Operation', foreign_key: :lato_user_id, dependent: :nullify
    has_many :lato_invitations, class_name: 'Lato::Invitation', foreign_key: :lato_user_id, dependent: :nullify
    has_many :lato_invitations_as_inviter, class_name: 'Lato::Invitation', foreign_key: :inviter_lato_user_id, dependent: :nullify

    has_many :lato_log_user_signins, class_name: 'Lato::Log::UserSignin', foreign_key: :lato_user_id, dependent: :nullify
    has_many :lato_log_user_signups, class_name: 'Lato::Log::UserSignup', foreign_key: :lato_user_id, dependent: :nullify

    # Hooks
    ##

    before_validation do
      self.email = email&.downcase&.strip
    end

    before_create do
      self.locale ||= I18n.default_locale
    end

    before_save do
      self.email_verified_at = nil if email_changed?
      self.accepted_privacy_policy_version = Lato.config.legal_privacy_policy_version if accepted_privacy_policy_version_changed?
      self.accepted_terms_and_conditions_version = Lato.config.legal_terms_and_conditions_version if accepted_terms_and_conditions_version_changed?
    end

    # Questions
    ##

    def valid_accepted_privacy_policy_version?
      @valid_accepted_privacy_policy_version ||= accepted_privacy_policy_version >= Lato.config.legal_privacy_policy_version
    end

    def valid_accepted_terms_and_conditions_version?
      @valid_accepted_terms_and_conditions_version ||= accepted_terms_and_conditions_version >= Lato.config.legal_terms_and_conditions_version
    end

    # Helpers
    ##

    def full_name
      "#{last_name} #{first_name}"
    end

    def gravatar_image_url(size = 200)
      @gravatar_image_url ||= "https://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}?s=#{size}"
    end

    # Operations
    ##

    def signup(params = {})
      return unless save

      begin
        lato_log_user_signups.create(
          ip_address: params[:ip_address],
          user_agent: params[:user_agent]
        )
      rescue StandardError => e
        Rails.logger.error(e)
      end

      true
    end

    def signin(params)
      self.email = params[:email]

      user = Lato::User.find_by(email: params[:email])
      unless user
        errors.add(:email, :not_correct)
        return
      end

      unless user.authenticate(params[:password])
        errors.add(:password, :not_correct)
        return
      end

      self.id = user.id
      reload

      begin
        lato_log_user_signins.create(
          ip_address: params[:ip_address],
          user_agent: params[:user_agent]
        )
      rescue StandardError => e
        Rails.logger.error(e)
      end

      true
    end

    def request_verify_email
      if c_email_verification_semaphore
        errors.add(:base, :email_verification_limit)
        return
      end

      code = SecureRandom.hex.upcase
      delivery = Lato::UserMailer.email_verification_mail(id, code).deliver_now
      unless delivery
        errors.add(:base, :email_sending_error)
        return
      end

      c_email_verification_code(code)
      c_email_verification_semaphore(true)

      true
    end

    def verify_email(params)
      email_verification_code = c_email_verification_code

      if email_verification_code.blank?
        errors.add(:base, :email_verification_code_expired)
        return
      end

      unless email_verification_code == params[:code]
        errors.add(:base, :email_verification_code_invalid)
        return
      end

      c_email_verification_code('')
      c_email_verification_semaphore(false)

      update_column(:email_verified_at, Time.now)
      true
    end

    def request_recover_password(params)
      user = Lato::User.find_by(email: params[:email])
      unless user
        errors.add(:email, :not_registered)
        return
      end

      code = SecureRandom.hex.upcase
      delivery = Lato::UserMailer.password_update_mail(user.id, code).deliver_now
      unless delivery
        errors.add(:base, :email_sending_error)
        return
      end

      self.id = user.id
      reload

      c_password_update_code(code)

      true
    end

    def update_password(params)
      password_update_code = c_password_update_code

      if password_update_code.blank?
        errors.add(:base, :password_update_code_expired)
        return
      end

      unless password_update_code == params[:code]
        errors.add(:base, :password_update_code_invalid)
        return
      end

      c_password_update_code('')

      update(params.permit(:password, :password_confirmation))
    end

    def update_accepted_privacy_policy_version(params)
      unless params[:confirm]
        errors.add(:base, :privacy_policy_invalid)
        return
      end

      update(accepted_privacy_policy_version: Lato.config.legal_privacy_policy_version)
    end

    def update_accepted_terms_and_conditions_version(params)
      unless params[:confirm]
        errors.add(:base, :terms_and_conditions_invalid)
        return
      end

      update(accepted_terms_and_conditions_version: Lato.config.legal_terms_and_conditions_version)
    end

    def destroy_with_confirmation(params)
      unless params[:email_confirmation] == email
        errors.add(:email, :not_correct)
        return
      end

      destroy
    end

    def accept_invitation(params)
      invitation = Lato::Invitation.find_by(id: params[:id], accepted_code: params[:accepted_code])
      if !invitation || invitation.accepted? || invitation.email != email
        errors.add(:base, :invitation_invalid)
        return
      end

      ActiveRecord::Base.transaction do
        raise ActiveRecord::Rollback unless save && invitation.update(
          accepted_at: Time.now,
          lato_user_id: id
        )

        true
      end
    end

    # Cache
    ##

    def c_email_verification_semaphore(value = nil)
      cache_key = "Lato::User/c_email_verification_semaphore/#{id}"
      return Rails.cache.read(cache_key) if value.nil?

      Rails.cache.write(cache_key, value, expires_in: 2.minutes)
      value
    end

    def c_email_verification_code(value = nil)
      cache_key = "Lato::User/c_email_verification_code/#{id}"
      return Rails.cache.read(cache_key) if value.nil?

      Rails.cache.write(cache_key, value, expires_in: 30.minutes)
      value
    end

    def c_password_update_code(value = nil)
      cache_key = "Lato::User/c_password_update_code/#{id}"
      return Rails.cache.read(cache_key) if value.nil?

      Rails.cache.write(cache_key, value, expires_in: 30.minutes)
      value
    end
  end
end