openjaf/cenit

View on GitHub
app/models/account.rb

Summary

Maintainability
C
1 day
Test Coverage
require 'cenit/heroku_client'

class Account
  include Setup::CenitUnscoped
  include Cenit::MultiTenancy
  include CredentialsGenerator
  include FieldsInspection
  include TimeZoneAware
  include ObserverTenantLookup

  DEFAULT_INDEX_MAX_ENTRIES = 100

  inspect_fields :name, :notification_level, :time_zone, :index_max_entries, :locked

  build_in_data_type.with(
    :name, :notification_level, :time_zone, :number, :authentication_token, :users, :locked
  ).including(
    :owner
  ).discarding(
     :owner, :users
  )
  build_in_data_type.protecting(:number, :authentication_token)
  build_in_data_type.and(
    properties: {
      unlocked: {
        type: 'boolean'
      },
      notification_level: {
        enum: Setup::SystemNotification.type_enum
      },
      time_zone: {
        enum: time_zone_enum
      },
      number: {
        type: 'string',
        edi: {
          segment: 'key'
        }
      },
      authentication_token: {
        type: 'string',
        edi: {
          segment: 'token'
        }
      }
    }
  )
  build_in_data_type.bulkable_deletable :denied

  deny :all

  belongs_to :owner, class_name: User.to_s, inverse_of: :accounts
  has_and_belongs_to_many :users, class_name: User.to_s, inverse_of: :member_accounts

  field :name, type: String
  field :meta, type: Hash, default: {}

  field :notification_level, type: StringifiedSymbol, default: :warning
  field :notifications_listed_at, type: DateTime

  field :index_max_entries, type: Integer, default: DEFAULT_INDEX_MAX_ENTRIES

  field :locked, type: Mongoid::Boolean, default: false

  validates_uniqueness_of :name, scope: :owner
  validates_inclusion_of :notification_level, in: ->(a) { a.notification_level_enum }

  before_validation do
    if self.owner ||= User.current
      if (n = name.to_s.strip).empty?
        n = owner.email
        c = 0
        while Account.where(owner_id: owner_id, name: n).exists?
          n = "#{owner.email} (#{c += 1})"
        end
      end
      self.name = n
    else
      errors.add(:base, 'can not be created outside current user context')
    end
    errors.blank?
  end

  before_create :check_creation_enabled

  before_save :init_heroku_db, :validates_configuration, :abort_unless_check_lock!

  after_destroy { clean_up }

  def abort_unless_check_lock!
    throw(:abort) unless check_lock
  end

  def check_lock
    if !new_record? && (changes = self.changes['locked']) &&
       !changes[1] && ::Cenit::MultiTenancy.user_model.current&.id != owner_id
      errors.add(:locked, 'can only be unlocked by the owner.')
      errors.add(:unlocked, 'can only be unlocked by the owner.')
    end
    errors.blank?
  end

  def check_lock!
    check_lock || fail(errors.full_messages.to_sentence)
  end

  def locked?
    locked
  end

  def unlocked?
    unlocked
  end

  def unlocked
    !locked?
  end

  def unlocked=(unlocked)
    self.locked = !unlocked
  end

  def enabled?
    unlocked?
  end

  def check_enabled
    enabled?
  end

  def check_enabled!
    check_enabled || fail("Tenant #{label} (ID #{id}) is not enabled")
  end

  def check_creation_enabled
    unless @for_create
      errors.add(:base, 'Tenant creation is disabled') if Cenit.tenant_creation_disabled && !User.super_access?
    end
    errors.blank?
  end

  def api_account
    self
  end

  def user
    owner
  end

  def read_raw_attribute(name)
    if !(value = super).nil? && (
      new_record? || !self.class.build_in_data_type.protecting?(name) ||
        ((current_user = User.try(:current)) && current_user.owns?(self)))
      value
    else
      nil
    end
  end

  def inspect_updated_fields
    users << owner unless user_ids.include?(owner.id)
    super
    if new_record?
      self.owner_id = owner&.id || User.current.id
    end
    generate_number
    ensure_token
  end

  def init_heroku_db
    if ENV['HEROKU_MLAB'] && new_record?
      heroku_name = "hub-#{id.to_s}"
      app = HerokuClient::App.create(heroku_name)
      if app
        if app.add_addon(ENV['HEROKU_MONGOPLAN'] || 'mongolab:sandbox')
          meta['db_name'] = heroku_name
          meta['db_uri'] = app.get_variable(ENV['HEROKU_MONGOVAR'] || 'MONGOLAB_URI')
        end
      end
    end
  end

  def validates_configuration
    remove_attribute(:index_max_entries) if index_max_entries < DEFAULT_INDEX_MAX_ENTRIES
    validates_time_zone
    abort_if_has_errors
  end

  def default_time_zone
    nil
  end

  def time_zone_offset
    super || owner&.time_zone_offset
  end

  def notification_level_enum
    Setup::SystemNotification.type_enum
  end

  def label
    l = name.to_s
    unless User.current == owner
      l += " of #{owner.present? ? owner.label : Account.to_s + '#' + id.to_s}"
    end
    l
  end

  def owner?(user)
    owner == user
  end

  def generate_number(options = {})
    options[:prefix] ||= 'A'
    super(options)
  end

  def super_admin?
    owner&.super_admin?
  end

  def sealed?
    owner&.sealed?
  end

  def clean_up
    switch do
      Cenit::ApplicationId.where(:id.in => Setup::Application.all.collect(&:application_id_id)).delete_all
    end
    TaskToken.where(tenant_id: id).delete_all
    Setup::DelayedMessage.where(tenant_id: id).destroy_all
    each_tenant_collection(&:drop)
  end

  def notify(attrs)
    switch { Setup::SystemNotification.create_with(attrs.merge(skip_notification_level: true)) }
  end

  def notification_span_for(type)
    (owner && owner.notification_span_for(type)) ||
      Cenit[:"default_#{type}_notifications_span"] || 1.hour
  end

  def owner_switch(&block)
    current_user = User.current
    User.current = owner
    switch(&block)
  ensure
    User.current = current_user
  end

  def get_owner
    fail 'Illegal access to tenant owner' unless User.super_access?
    owner
  end

  def for_create?
    @for_create || false
  end

  class << self

    def find_where(expression)
      scope = where(expression)
      unless User.super_access?
        user_id = (user = User.current) && user.id
        member_account_ids = user&.member_account_ids
        scope = scope.and({ '$or' => [
          { 'owner_id' => user_id },
          { '_id' => { '$in' => member_account_ids || [] } }
        ] })
      end
      scope
    end

    def find_all
      find_where({})
    end

    def notify(attrs)
      current&.notify(attrs)
    end

    def current_id
      current&.id
    end

    def current_key
      (current&.number) || 'XXXXXXX'
    end

    def current_token
      (current&.token) || 'XXXXXXXXXXXXXXXX'
    end

    def new_for_create(params = {})
      account = new(params)
      account.instance_variable_set(:@for_create, true)
      account
    end

    def set_current_with_connection(key, token)
      all.each do |account|
        self.current = account
        if (connection = Setup::Connection.where(number: key).first).present? && Devise.secure_compare(connection.token, token)
          return connection
        end
      end
      self.current = nil
    end

    def data_type_collection_name(data_type)
      tenant_collection_name(data_type.data_type_name)
    end

    def notification_span_for(type)
      ((current = self.current) && current.notification_span_for(type)) ||
        Cenit[:"default_#{type}_notifications_span"] || 1.hour
    end
  end
end

Tenant = Account