293 lines
8.1 KiB
Markdown
293 lines
8.1 KiB
Markdown
## Code Context
|
|
|
|
**Query:** contact management, contact inbox, custom attribute definition, custom filter, contact merge, contact search, CRM, note, label
|
|
|
|
### Entry Points
|
|
|
|
- **AddRegexToCustomAttributeDefinition** (class) - db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb:1
|
|
- **CustomAttributeDefinition** (class) - app/models/custom_attribute_definition.rb:24
|
|
- **CustomAttributeDefinitionPolicy** (class) - app/policies/custom_attribute_definition_policy.rb:1
|
|
|
|
### Related Symbols
|
|
|
|
- app/models/application_record.rb: ApplicationRecord:1
|
|
- app/policies/application_policy.rb: ApplicationPolicy:1
|
|
- db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb: change:2
|
|
- app/models/custom_attribute_definition.rb: normalize_attribute_fields:54, sync_widget_pre_chat_custom_fields:59, update_widget_pre_chat_custom_fields:63, attribute_must_not_conflict:67
|
|
- app/policies/custom_attribute_definition_policy.rb: index?:2, show?:6, create?:10
|
|
|
|
### Code
|
|
|
|
#### AddRegexToCustomAttributeDefinition (db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb:1)
|
|
|
|
```ruby
|
|
class AddRegexToCustomAttributeDefinition < ActiveRecord::Migration[7.0]
|
|
def change
|
|
add_column :custom_attribute_definitions, :regex_pattern, :string
|
|
add_column :custom_attribute_definitions, :regex_cue, :string
|
|
end
|
|
end
|
|
```
|
|
|
|
#### CustomAttributeDefinition (app/models/custom_attribute_definition.rb:24)
|
|
|
|
```ruby
|
|
class CustomAttributeDefinition < ApplicationRecord
|
|
STANDARD_ATTRIBUTES = {
|
|
:conversation => %w[status priority assignee_id inbox_id team_id display_id campaign_id labels browser_language country_code referer created_at
|
|
last_activity_at],
|
|
:contact => %w[name email phone_number identifier country_code city company_name created_at last_activity_at referer blocked],
|
|
:company => %w[name domain description contacts_count created_at updated_at last_activity_at]
|
|
}.freeze
|
|
|
|
scope :with_attribute_model, ->(attribute_model) { attribute_model.presence && where(attribute_model: attribute_model) }
|
|
validates :attribute_display_name, presence: true
|
|
before_validation :normalize_attribute_fields
|
|
|
|
validates :attribute_key,
|
|
presence: true,
|
|
uniqueness: { scope: [:account_id, :attribute_model] },
|
|
format: { with: /\A[\p{L}\p{N}_.\-]+\z/, message: I18n.t('errors.custom_attribute_definition.attribute_key_format') }
|
|
|
|
validates :attribute_display_type, presence: true
|
|
validates :attribute_model, presence: true
|
|
validate :attribute_must_not_conflict, on: :create
|
|
|
|
enum attribute_model: { conversation_attribute: 0, contact_attribute: 1, company_attribute: 2 }
|
|
enum attribute_display_type: { text: 0, number: 1, currency: 2, percent: 3, link: 4, date: 5, list: 6, checkbox: 7 }
|
|
|
|
belongs_to :account
|
|
after_update :update_widget_pre_chat_custom_fields, unless: :company_attribute?
|
|
after_destroy :sync_widget_pre_chat_
|
|
// ... truncated ...
|
|
```
|
|
|
|
#### CustomAttributeDefinitionPolicy (app/policies/custom_attribute_definition_policy.rb:1)
|
|
|
|
```ruby
|
|
class CustomAttributeDefinitionPolicy < ApplicationPolicy
|
|
def index?
|
|
@account_user.administrator? || @account_user.agent?
|
|
end
|
|
|
|
def show?
|
|
@account_user.administrator? || @account_user.agent?
|
|
end
|
|
|
|
def create?
|
|
@account_user.administrator?
|
|
end
|
|
|
|
def update?
|
|
@account_user.administrator?
|
|
end
|
|
|
|
def destroy?
|
|
@account_user.administrator?
|
|
end
|
|
end
|
|
```
|
|
|
|
#### change (db/migrate/20230905060223_add_regex_to_custom_attribute_definition.rb:2)
|
|
|
|
```ruby
|
|
def change
|
|
add_column :custom_attribute_definitions, :regex_pattern, :string
|
|
add_column :custom_attribute_definitions, :regex_cue, :string
|
|
end
|
|
```
|
|
|
|
#### normalize_attribute_fields (app/models/custom_attribute_definition.rb:54)
|
|
|
|
```ruby
|
|
def normalize_attribute_fields
|
|
self.attribute_key = attribute_key.strip if attribute_key.present?
|
|
self.attribute_display_name = attribute_display_name.strip if attribute_display_name.present?
|
|
end
|
|
```
|
|
|
|
#### sync_widget_pre_chat_custom_fields (app/models/custom_attribute_definition.rb:59)
|
|
|
|
```ruby
|
|
def sync_widget_pre_chat_custom_fields
|
|
::Inboxes::SyncWidgetPreChatCustomFieldsJob.perform_later(account, attribute_key)
|
|
end
|
|
```
|
|
|
|
#### update_widget_pre_chat_custom_fields (app/models/custom_attribute_definition.rb:63)
|
|
|
|
```ruby
|
|
def update_widget_pre_chat_custom_fields
|
|
::Inboxes::UpdateWidgetPreChatCustomFieldsJob.perform_later(account, self)
|
|
end
|
|
```
|
|
|
|
#### attribute_must_not_conflict (app/models/custom_attribute_definition.rb:67)
|
|
|
|
```ruby
|
|
def attribute_must_not_conflict
|
|
model_keys = attribute_model.to_s.delete_suffix('_attribute').to_sym
|
|
standard_attributes = STANDARD_ATTRIBUTES[model_keys]
|
|
return if standard_attributes.blank?
|
|
return unless attribute_key.in?(standard_attributes)
|
|
|
|
errors.add(:attribute_key, I18n.t('errors.custom_attribute_definition.key_conflict'))
|
|
end
|
|
```
|
|
|
|
#### index? (app/policies/custom_attribute_definition_policy.rb:2)
|
|
|
|
```ruby
|
|
def index?
|
|
@account_user.administrator? || @account_user.agent?
|
|
end
|
|
```
|
|
|
|
#### show? (app/policies/custom_attribute_definition_policy.rb:6)
|
|
|
|
```ruby
|
|
def show?
|
|
@account_user.administrator? || @account_user.agent?
|
|
end
|
|
```
|
|
|
|
#### create? (app/policies/custom_attribute_definition_policy.rb:10)
|
|
|
|
```ruby
|
|
def create?
|
|
@account_user.administrator?
|
|
end
|
|
```
|
|
|
|
#### update? (app/policies/custom_attribute_definition_policy.rb:14)
|
|
|
|
```ruby
|
|
def update?
|
|
@account_user.administrator?
|
|
end
|
|
```
|
|
|
|
#### destroy? (app/policies/custom_attribute_definition_policy.rb:18)
|
|
|
|
```ruby
|
|
def destroy?
|
|
@account_user.administrator?
|
|
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 ...
|
|
```
|
|
|
|
#### 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
|
|
``` |