清理: - 删除 34 份过时文档(gap reports/QA临时报告/验收报告/阶段性文档) - 删除 docs/.hermes/skills 第三方 skills 副本(16 文件) - 删除 skills-lock.json 目录归集: - 根目录仅保留 README.md 索引 - product/ — 产品与架构设计(PRD + ARCHITECTURE + P2设计文档 + AI/企业路线图) - tracking/ — Chatwoot parity 开发跟踪 - requirements/ — M01-M12 模块需求 - plans/ — 历史实现计划 - parity/ — 路由 parity 与前端契约 - qa/ — QA 报告与测试计划 - ops/ — 运维部署 命名规范: - 全小写 kebab-case,禁止全大写文件名 - product/tracking/ops 用 NN- 序号前缀 - requirements 用 MNN- 两位零填充模块号 - plans/qa 用 YYYY-MM-DD- 日期前缀 - requirements M1-M9 零填充为 M01-M09(修复字典序) 同步更新: - backend/cmd/route_parity/main.go 路径默认值 - backend/scripts/parity_frontend_smoke.sh 报告路径 - 所有 docs 内部交叉引用 - .gitignore 排除编译产物 (backend/gochat, backend/route_parity) - 新增迁移 000052/000053 - 前端 WS 相关修改
8.1 KiB
8.1 KiB
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)
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)
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)
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)
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)
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)
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)
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)
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)
def index?
@account_user.administrator? || @account_user.agent?
end
show? (app/policies/custom_attribute_definition_policy.rb:6)
def show?
@account_user.administrator? || @account_user.agent?
end
create? (app/policies/custom_attribute_definition_policy.rb:10)
def create?
@account_user.administrator?
end
update? (app/policies/custom_attribute_definition_policy.rb:14)
def update?
@account_user.administrator?
end
destroy? (app/policies/custom_attribute_definition_policy.rb:18)
def destroy?
@account_user.administrator?
end
ApplicationRecord (app/models/application_record.rb:1)
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)
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