second commit
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
## Code Context
|
||||
|
||||
**Query:** enterprise features, SLA policy, applied SLA, SLA event, audit log, custom role, company, SAML settings, agent capacity, call, voice通话
|
||||
|
||||
### Entry Points
|
||||
|
||||
- **AccountSamlSettingsPolicy** (class) - enterprise/app/policies/account_saml_settings_policy.rb:1
|
||||
- **Company** (class) - enterprise/app/models/company.rb:23
|
||||
- **AddAgentCapacityPolicyToAccountUsers** (class) - db/migrate/20250806140004_add_agent_capacity_policy_to_account_users.rb:3
|
||||
|
||||
### Related Symbols
|
||||
|
||||
- app/policies/application_policy.rb: ApplicationPolicy:1
|
||||
- app/models/application_record.rb: ApplicationRecord:1
|
||||
- enterprise/app/policies/account_saml_settings_policy.rb: show?:2, create?:6, update?:10, destroy?:14
|
||||
- enterprise/app/models/company.rb: record_activity_at!:63, prepare_jsonb_attributes:71, fetch_favicon:76
|
||||
- app/jobs/migration/backfill_companies_contacts_count_job.rb: perform:4
|
||||
|
||||
### Code
|
||||
|
||||
#### AccountSamlSettingsPolicy (enterprise/app/policies/account_saml_settings_policy.rb:1)
|
||||
|
||||
```ruby
|
||||
class AccountSamlSettingsPolicy < ApplicationPolicy
|
||||
def show?
|
||||
@account_user.administrator?
|
||||
end
|
||||
|
||||
def create?
|
||||
@account_user.administrator?
|
||||
end
|
||||
|
||||
def update?
|
||||
@account_user.administrator?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
@account_user.administrator?
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### Company (enterprise/app/models/company.rb:23)
|
||||
|
||||
```ruby
|
||||
class Company < ApplicationRecord
|
||||
include Avatarable
|
||||
|
||||
ACTIVITY_ROLLUP_INTERVAL = 5.minutes
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :name, presence: true, length: { maximum: Limits::COMPANY_NAME_LENGTH_LIMIT }
|
||||
validates :domain, allow_blank: true, format: {
|
||||
with: /\A[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+\z/,
|
||||
message: I18n.t('errors.companies.domain.invalid')
|
||||
}
|
||||
validates :domain, uniqueness: { scope: :account_id }, if: -> { domain.present? }
|
||||
validates :description, length: { maximum: Limits::COMPANY_DESCRIPTION_LENGTH_LIMIT }
|
||||
validates :custom_attributes, jsonb_attributes_length: true
|
||||
|
||||
belongs_to :account
|
||||
has_many :contacts, dependent: :nullify
|
||||
before_validation :prepare_jsonb_attributes
|
||||
after_create_commit :fetch_favicon, if: -> { domain.present? }
|
||||
|
||||
scope :ordered_by_name, -> { order(:name) }
|
||||
scope :search_by_name_or_domain, lambda { |query|
|
||||
where('name ILIKE :search OR domain ILIKE :search', search: "%#{query.strip}%")
|
||||
}
|
||||
|
||||
scope :order_on_contacts_count, lambda { |direction|
|
||||
order(
|
||||
Arel::Nodes::SqlLiteral.new(
|
||||
sanitize_sql_for_order("\"companies\".\"contacts_count\" #{direction} NULLS LAST")
|
||||
)
|
||||
)
|
||||
}
|
||||
scope :order_on_last_activity_at, lambda { |direction|
|
||||
order(
|
||||
Arel::Nodes::SqlLiteral.new(
|
||||
sanitize_sql_for_order("\"companies\".\"last_activity_at\" #{direction} NULLS LAST")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def record_activity_at!(activity_at)
|
||||
|
||||
// ... truncated ...
|
||||
```
|
||||
|
||||
#### AddAgentCapacityPolicyToAccountUsers (db/migrate/20250806140004_add_agent_capacity_policy_to_account_users.rb:3)
|
||||
|
||||
```ruby
|
||||
class AddAgentCapacityPolicyToAccountUsers < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_reference :account_users, :agent_capacity_policy, null: true, index: true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### show? (enterprise/app/policies/account_saml_settings_policy.rb:2)
|
||||
|
||||
```ruby
|
||||
def show?
|
||||
@account_user.administrator?
|
||||
end
|
||||
```
|
||||
|
||||
#### create? (enterprise/app/policies/account_saml_settings_policy.rb:6)
|
||||
|
||||
```ruby
|
||||
def create?
|
||||
@account_user.administrator?
|
||||
end
|
||||
```
|
||||
|
||||
#### update? (enterprise/app/policies/account_saml_settings_policy.rb:10)
|
||||
|
||||
```ruby
|
||||
def update?
|
||||
@account_user.administrator?
|
||||
end
|
||||
```
|
||||
|
||||
#### destroy? (enterprise/app/policies/account_saml_settings_policy.rb:14)
|
||||
|
||||
```ruby
|
||||
def destroy?
|
||||
@account_user.administrator?
|
||||
end
|
||||
```
|
||||
|
||||
#### record_activity_at! (enterprise/app/models/company.rb:63)
|
||||
|
||||
```ruby
|
||||
def record_activity_at!(activity_at)
|
||||
return if last_activity_at.present? && last_activity_at > activity_at - ACTIVITY_ROLLUP_INTERVAL
|
||||
|
||||
update!(last_activity_at: activity_at)
|
||||
end
|
||||
```
|
||||
|
||||
#### prepare_jsonb_attributes (enterprise/app/models/company.rb:71)
|
||||
|
||||
```ruby
|
||||
def prepare_jsonb_attributes
|
||||
self.additional_attributes = {} unless additional_attributes.is_a?(Hash)
|
||||
self.custom_attributes = {} unless custom_attributes.is_a?(Hash)
|
||||
end
|
||||
```
|
||||
|
||||
#### fetch_favicon (enterprise/app/models/company.rb:76)
|
||||
|
||||
```ruby
|
||||
def fetch_favicon
|
||||
Avatar::AvatarFromFaviconJob.set(wait: 5.seconds).perform_later(self)
|
||||
end
|
||||
```
|
||||
|
||||
#### perform (app/jobs/migration/backfill_companies_contacts_count_job.rb:4)
|
||||
|
||||
```ruby
|
||||
def perform
|
||||
return unless ChatwootApp.enterprise?
|
||||
|
||||
Company.find_in_batches(batch_size: 100) do |company_batch|
|
||||
company_batch.each do |company|
|
||||
Company.reset_counters(company.id, :contacts)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### find_or_create_company (enterprise/app/jobs/migration/company_account_batch_job.rb:32)
|
||||
|
||||
```ruby
|
||||
def find_or_create_company(contact, account)
|
||||
domain = extract_domain(contact.email)
|
||||
company_name = derive_company_name(contact, domain)
|
||||
|
||||
Company.find_or_create_by!(account: account, domain: domain) do |company|
|
||||
company.name = company_name
|
||||
end
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# Race condition: Another job created it between our check and create
|
||||
# just find the one that was created
|
||||
|
||||
Company.find_by(account: account, domain: domain)
|
||||
end
|
||||
```
|
||||
|
||||
#### associate_company_from_email (enterprise/app/services/contacts/company_association_service.rb:2)
|
||||
|
||||
```ruby
|
||||
def associate_company_from_email(contact)
|
||||
return nil if skip_association?(contact)
|
||||
|
||||
company = find_or_create_company(contact)
|
||||
if company
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
# Using update_column and increment_counter to avoid triggering callbacks while maintaining counter cache
|
||||
contact.update_column(:company_id, company.id)
|
||||
Company.increment_counter(:contacts_count, company.id)
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
company.record_activity_at!(contact.last_activity_at) if contact.last_activity_at.present?
|
||||
end
|
||||
company
|
||||
end
|
||||
```
|
||||
|
||||
#### find_or_create_company (enterprise/app/services/contacts/company_association_service.rb:29)
|
||||
|
||||
```ruby
|
||||
def find_or_create_company(contact)
|
||||
domain = extract_domain(contact.email)
|
||||
company_name = derive_company_name(contact, domain)
|
||||
|
||||
Company.find_or_create_by!(account: contact.account, domain: domain) do |company|
|
||||
company.name = company_name
|
||||
end
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# If another process created it first, just find that
|
||||
Company.find_by(account: contact.account, domain: domain)
|
||||
end
|
||||
```
|
||||
|
||||
#### change (db/migrate/20250806140004_add_agent_capacity_policy_to_account_users.rb:4)
|
||||
|
||||
```ruby
|
||||
def change
|
||||
add_reference :account_users, :agent_capacity_policy, null: true, index: true
|
||||
end
|
||||
```
|
||||
|
||||
#### ApplicationPolicy (app/policies/application_policy.rb:1)
|
||||
|
||||
```ruby
|
||||
class ApplicationPolicy
|
||||
attr_reader :user_context, :user, :record, :account, :account_user
|
||||
|
||||
def initialize(user_context, record)
|
||||
@user_context = user_context
|
||||
@user = user_context[:user]
|
||||
@account = user_context[:account]
|
||||
@account_user = user_context[:account_user]
|
||||
@record = record
|
||||
end
|
||||
|
||||
def index?
|
||||
false
|
||||
end
|
||||
|
||||
def show?
|
||||
scope.exists?(id: record.id)
|
||||
end
|
||||
|
||||
def create?
|
||||
false
|
||||
end
|
||||
|
||||
def new?
|
||||
create?
|
||||
end
|
||||
|
||||
def update?
|
||||
false
|
||||
end
|
||||
|
||||
def edit?
|
||||
update?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
false
|
||||
end
|
||||
|
||||
def scope
|
||||
Pundit.policy_scope!(user_context, record.class)
|
||||
end
|
||||
|
||||
class Scope
|
||||
attr_reader :user_context, :user, :scope, :account, :account_user
|
||||
|
||||
def initialize(user_context, scope)
|
||||
@user_context = user_context
|
||||
@user = user_context[:user]
|
||||
@account = user_context[:account]
|
||||
@account_user = user_context[:account_user]
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def resolve
|
||||
scope
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### ApplicationRecord (app/models/application_record.rb:1)
|
||||
|
||||
```ruby
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
include Events::Types
|
||||
self.abstract_class = true
|
||||
|
||||
before_validation :validates_column_content_length
|
||||
|
||||
# the models that exposed in email templates through liquid
|
||||
def droppables
|
||||
%w[Account Channel Conversation Inbox User Message]
|
||||
end
|
||||
|
||||
# ModelDrop class should exist in app/drops
|
||||
def to_drop
|
||||
return unless droppables.include?(self.class.name)
|
||||
|
||||
"#{self.class.name}Drop".constantize.new(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Generic validation for all columns of type string and text
|
||||
# Validates the length of the column to prevent DOS via large payloads
|
||||
# if a custom length validation is already present, skip the validation
|
||||
def validates_column_content_length
|
||||
self.class.columns.each do |column|
|
||||
check_and_validate_content_length(column) if column_of_type_string_or_text?(column)
|
||||
end
|
||||
end
|
||||
|
||||
def column_of_type_string_or_text?(column)
|
||||
%i[string text].include?(column.type)
|
||||
end
|
||||
|
||||
def check_and_validate_content_length(column)
|
||||
length_validator = self.class.validators_on(column.name).find { |v| v.kind == :length }
|
||||
validate_content_length(column) if length_validator.blank?
|
||||
end
|
||||
|
||||
def validate_content_length(column)
|
||||
max_length = column.type == :text ? 20_000 : 255
|
||||
return if self[column.name].nil? || self[column.name].length <= max_length
|
||||
|
||||
errors.add(column.name.to_sym, "is too long (maximum is #{max_length} characters)")
|
||||
end
|
||||
|
||||
def normalize_empty_string_to_nil(attrs = [])
|
||||
attrs
|
||||
// ... truncated ...
|
||||
```
|
||||
Reference in New Issue
Block a user