HabitatMap/AirCasting

View on GitHub
app/models/user.rb

Summary

Maintainability
A
3 hrs
Test Coverage
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  #   :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable,
         :registerable,
         :recoverable,
         :rememberable,
         :trackable,
         :validatable

  has_many :sessions, inverse_of: :user, dependent: :destroy
  has_many :mobile_sessions, inverse_of: :user
  has_many :fixed_sessions, inverse_of: :user
  has_many :streams, through: :sessions
  has_many :measurements, through: :streams
  has_many :threshold_alerts, dependent: :destroy

  # Virtual attribute for devise
  attr_accessor :login

  before_create :ensure_authentication_token
  before_save :chomp_username_attribute!

  validates :username, presence: true
  validates_uniqueness_of :username, case_sensitive: false
  validates_uniqueness_of :email, case_sensitive: false

  # TokenAuthenticatable was removed from Devise in 3.1
  # https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
  def ensure_authentication_token
    if authentication_token.blank?
      self.authentication_token = generate_authentication_token
    end
  end

  def as_json(*args)
    super(
      only: %i[id email username authentication_token session_stopped_alert],
    )
  end

  # Inspired by Devise wiki
  # https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address
  # https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign_in-using-their-username-or-email-address
  def self.find_for_database_authentication(conditions)
    conditions = conditions.dup

    # login if coming from mobile || email if coming from web
    login = conditions.delete(:login) || conditions.fetch(:email)

    where(
      [
        'authentication_token = :token OR ' + 'lower(username) = :value OR ' +
          'lower(email) = :value',
        { value: login.downcase, token: login },
      ],
    ).first
  end

  # Inspired by Devise wiki
  def self.send_reset_password_instructions(conditions = {})
    record = find_for_database_authentication(conditions)
    record.send_reset_password_instructions if record
    record
  end

  def sync(data)
    upload = []
    deleted = []

    data.each do |session_data|
      uuid = session_data[:uuid]
      already_deleted_session =
        DeletedSession.where(uuid: uuid, user_id: self.id).first

      if already_deleted_session
        deleted << already_deleted_session[:uuid]
      else
        session = sessions.find_by_uuid(uuid)

        if session
          if session_data[:deleted]
            session.destroy
            already_deleted_session =
              DeletedSession.where(uuid: uuid, user_id: self.id).first
            deleted << already_deleted_session[:uuid]
          else
            session.sync(session_data)
          end
        elsif !session && !session_data[:deleted]
          # session hasn't been yet uploaded by the mobile app
          upload << uuid
        else
          # session was not found && session_data[:deleted] == true
        end
      end
    end

    uuids = data.map { |x| x[:uuid] }
    download =
      sessions
        .where.not(uuid: uuids)
        .select do |session|
          (session.streams.count != 0) &&
            (session.streams.all? { |stream| stream.measurements.count != 0 })
        end
        .map(&:id)

    { upload: upload, download: download, deleted: deleted }
  end

  def admin?
    read_attribute(:admin) || email.eql?('admin@aircasting.org')
  end

  private

  def chomp_username_attribute!
    self.username.chomp!
  end

  # TokenAuthenticatable was removed from Devise in 3.1
  # https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end
end