Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0889aec755 | ||
|
|
53b9ed49c4 | ||
|
|
c93528e1b3 | ||
|
|
0899021777 | ||
|
|
db25b7d944 | ||
|
|
67a465fb01 | ||
|
|
92abd6224e | ||
|
|
5fcf47bc86 | ||
|
|
4b4afc4c52 | ||
|
|
edc27a155b |
@@ -65,6 +65,8 @@ class ContactInboxBuilder
|
||||
source_id: @source_id
|
||||
}
|
||||
|
||||
update_contact_verification_status
|
||||
|
||||
::ContactInbox.where(attrs).first_or_create!(hmac_verified: hmac_verified || false)
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
Rails.logger.info("[ContactInboxBuilder] RecordNotUnique #{@source_id} #{@contact.id} #{@inbox.id}")
|
||||
@@ -102,4 +104,18 @@ class ContactInboxBuilder
|
||||
def allowed_channels?
|
||||
@inbox.email? || @inbox.sms? || @inbox.twilio? || @inbox.whatsapp?
|
||||
end
|
||||
|
||||
def update_contact_verification_status
|
||||
return if @contact.is_verified
|
||||
return unless should_verify_contact?
|
||||
|
||||
@contact.update!(is_verified: true)
|
||||
end
|
||||
|
||||
def should_verify_contact?
|
||||
# Social channels where contacts should be verified by default
|
||||
@inbox.facebook? || @inbox.instagram? || @inbox.instagram_direct? ||
|
||||
@inbox.twitter? || @inbox.line? || @inbox.whatsapp? ||
|
||||
@inbox.telegram?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,6 +66,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
contacts = result[:contacts]
|
||||
@contacts_count = result[:count]
|
||||
@contacts = fetch_contacts(contacts)
|
||||
|
||||
rescue CustomExceptions::CustomFilter::InvalidAttribute,
|
||||
CustomExceptions::CustomFilter::InvalidOperator,
|
||||
CustomExceptions::CustomFilter::InvalidQueryOperator,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Delete migration and spec after 2 consecutive releases.
|
||||
class Migration::PopulateContactVerificationJob < ApplicationJob
|
||||
include Sidekiq::Worker
|
||||
sidekiq_options queue: :scheduled_jobs, timeout: 3_600 # 1 hour
|
||||
|
||||
def perform
|
||||
updated_count = 0
|
||||
|
||||
Contact.find_each(batch_size: 1000) do |contact|
|
||||
next if contact.is_verified
|
||||
next unless should_verify_contact?(contact)
|
||||
|
||||
contact.update_columns(is_verified: true) # rubocop:disable Rails/SkipsModelValidations
|
||||
updated_count += 1
|
||||
end
|
||||
|
||||
Rails.logger.info "[PopulateContactVerificationJob] Updated #{updated_count} contacts to verified"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def should_verify_contact?(contact)
|
||||
verification_attributes?(contact) || social_channel_inbox?(contact)
|
||||
end
|
||||
|
||||
def verification_attributes?(contact)
|
||||
contact.email.present? ||
|
||||
contact.phone_number.present? ||
|
||||
contact.identifier.present?
|
||||
end
|
||||
|
||||
def social_channel_inbox?(contact)
|
||||
contact.inboxes.any? { |inbox| social_channel?(inbox) }
|
||||
end
|
||||
|
||||
def social_channel?(inbox)
|
||||
inbox.facebook? || inbox.instagram? || inbox.instagram_direct? ||
|
||||
inbox.twitter? || inbox.line? || inbox.whatsapp? ||
|
||||
inbox.telegram?
|
||||
end
|
||||
end
|
||||
+14
-2
@@ -12,6 +12,7 @@
|
||||
# custom_attributes :jsonb
|
||||
# email :string
|
||||
# identifier :string
|
||||
# is_verified :boolean default(FALSE), not null
|
||||
# last_activity_at :datetime
|
||||
# last_name :string default("")
|
||||
# location :string default("")
|
||||
@@ -25,8 +26,10 @@
|
||||
# Indexes
|
||||
#
|
||||
# index_contacts_on_account_id (account_id)
|
||||
# index_contacts_on_account_id_and_is_verified (account_id,is_verified)
|
||||
# index_contacts_on_account_id_and_last_activity_at (account_id,last_activity_at DESC NULLS LAST)
|
||||
# index_contacts_on_blocked (blocked)
|
||||
# index_contacts_on_is_verified (is_verified)
|
||||
# index_contacts_on_lower_email_account_id (lower((email)::text), account_id)
|
||||
# index_contacts_on_name_email_phone_number_identifier (name,email,phone_number,identifier) USING gin
|
||||
# index_contacts_on_nonempty_fields (account_id,email,phone_number,identifier) WHERE (((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))
|
||||
@@ -63,7 +66,7 @@ class Contact < ApplicationRecord
|
||||
after_create_commit :dispatch_create_event, :ip_lookup
|
||||
after_update_commit :dispatch_update_event
|
||||
after_destroy_commit :dispatch_destroy_event
|
||||
before_save :sync_contact_attributes
|
||||
before_save :sync_contact_attributes, :update_is_verified
|
||||
|
||||
enum contact_type: { visitor: 0, lead: 1, customer: 2 }
|
||||
|
||||
@@ -176,7 +179,7 @@ class Contact < ApplicationRecord
|
||||
end
|
||||
|
||||
def self.resolved_contacts
|
||||
where("contacts.email <> '' OR contacts.phone_number <> '' OR contacts.identifier <> ''")
|
||||
where(is_verified: true)
|
||||
end
|
||||
|
||||
def discard_invalid_attrs
|
||||
@@ -238,4 +241,13 @@ class Contact < ApplicationRecord
|
||||
def dispatch_destroy_event
|
||||
Rails.configuration.dispatcher.dispatch(CONTACT_DELETED, Time.zone.now, contact: self)
|
||||
end
|
||||
|
||||
def update_is_verified
|
||||
# Once verified, always verified - don't change back to false
|
||||
return if is_verified
|
||||
|
||||
self.is_verified = email.present? ||
|
||||
phone_number.present? ||
|
||||
identifier.present?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -135,6 +135,14 @@ class Inbox < ApplicationRecord
|
||||
channel_type == 'Channel::Email'
|
||||
end
|
||||
|
||||
def line?
|
||||
channel_type == 'Channel::Line'
|
||||
end
|
||||
|
||||
def telegram?
|
||||
channel_type == 'Channel::Telegram'
|
||||
end
|
||||
|
||||
def twilio?
|
||||
channel_type == 'Channel::TwilioSms'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class AddIsVerifiedToContacts < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :contacts, :is_verified, :boolean, default: false, null: false
|
||||
add_index :contacts, :is_verified
|
||||
add_index :contacts, [:account_id, :is_verified], name: 'index_contacts_on_account_id_and_is_verified'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class PopulateContactVerificationForSocialChannels < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
Migration::PopulateContactVerificationJob.perform_later
|
||||
end
|
||||
end
|
||||
+4
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2025_07_14_104358) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2025_07_22_153206) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -538,14 +538,17 @@ ActiveRecord::Schema[7.1].define(version: 2025_07_14_104358) do
|
||||
t.string "location", default: ""
|
||||
t.string "country_code", default: ""
|
||||
t.boolean "blocked", default: false, null: false
|
||||
t.boolean "is_verified", default: false, null: false
|
||||
t.index "lower((email)::text), account_id", name: "index_contacts_on_lower_email_account_id"
|
||||
t.index ["account_id", "email", "phone_number", "identifier"], name: "index_contacts_on_nonempty_fields", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))"
|
||||
t.index ["account_id", "is_verified"], name: "index_contacts_on_account_id_and_is_verified"
|
||||
t.index ["account_id", "last_activity_at"], name: "index_contacts_on_account_id_and_last_activity_at", order: { last_activity_at: "DESC NULLS LAST" }
|
||||
t.index ["account_id"], name: "index_contacts_on_account_id"
|
||||
t.index ["account_id"], name: "index_resolved_contact_account_id", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))"
|
||||
t.index ["blocked"], name: "index_contacts_on_blocked"
|
||||
t.index ["email", "account_id"], name: "uniq_email_per_account_contact", unique: true
|
||||
t.index ["identifier", "account_id"], name: "uniq_identifier_per_account_contact", unique: true
|
||||
t.index ["is_verified"], name: "index_contacts_on_is_verified"
|
||||
t.index ["name", "email", "phone_number", "identifier"], name: "index_contacts_on_name_email_phone_number_identifier", opclass: :gin_trgm_ops, using: :gin
|
||||
t.index ["phone_number", "account_id"], name: "index_contacts_on_phone_number_and_account_id"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user