mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 18:47:51 +00:00
feat: Contactable Inboxes API (#2101)
- Add endpoint which lists inboxes through which a contact can be contacted - Conversation creation API auto-creates contact_inbox for specific channels [ Twilio, email, api] - Ability to send the initial message payload along with the conversation creation - Fixes #1678 ( issue saving additional attributes for conversation )
This commit is contained in:
41
app/builders/contact_inbox_builder.rb
Normal file
41
app/builders/contact_inbox_builder.rb
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
class ContactInboxBuilder
|
||||||
|
pattr_initialize [:contact_id!, :inbox_id!, :source_id]
|
||||||
|
|
||||||
|
def perform
|
||||||
|
@contact = Contact.find(contact_id)
|
||||||
|
@inbox = @contact.account.inboxes.find(inbox_id)
|
||||||
|
return unless ['Channel::TwilioSms', 'Channel::Email', 'Channel::Api'].include? @inbox.channel_type
|
||||||
|
|
||||||
|
source_id = @source_id || generate_source_id
|
||||||
|
create_contact_inbox(source_id) if source_id.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def generate_source_id
|
||||||
|
return twilio_source_id if @inbox.channel_type == 'Channel::TwilioSms'
|
||||||
|
return @contact.email if @inbox.channel_type == 'Channel::Email'
|
||||||
|
return SecureRandom.uuid if @inbox.channel_type == 'Channel::Api'
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def twilio_source_id
|
||||||
|
return unless @contact.phone_number
|
||||||
|
|
||||||
|
case @inbox.channel.medium
|
||||||
|
when 'sms'
|
||||||
|
@contact.phone_number
|
||||||
|
when 'whatsapp'
|
||||||
|
"whatsapp:#{@contact.phone_number}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_contact_inbox(source_id)
|
||||||
|
::ContactInbox.find_or_create_by!(
|
||||||
|
contact_id: @contact.id,
|
||||||
|
inbox_id: @inbox.id,
|
||||||
|
source_id: source_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,7 +4,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
|||||||
|
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :set_current_page, only: [:index, :active, :search]
|
before_action :set_current_page, only: [:index, :active, :search]
|
||||||
before_action :fetch_contact, only: [:show, :update]
|
before_action :fetch_contact, only: [:show, :update, :contactable_inboxes]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@contacts_count = resolved_contacts.count
|
@contacts_count = resolved_contacts.count
|
||||||
@@ -40,6 +40,10 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
|||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
|
def contactable_inboxes
|
||||||
|
@contactable_inboxes = Contacts::ContactableInboxesService.new(contact: @contact).get
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
@contact = Current.account.contacts.new(contact_params)
|
@contact = Current.account.contacts.new(contact_params)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
user = current_user || @resource
|
user = Current.user || @resource
|
||||||
mb = Messages::MessageBuilder.new(user, @conversation, params)
|
mb = Messages::MessageBuilder.new(user, @conversation, params)
|
||||||
@message = mb.perform
|
@message = mb.perform
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@conversation = ::Conversation.create!(conversation_params)
|
ActiveRecord::Base.transaction do
|
||||||
|
@conversation = ::Conversation.create!(conversation_params)
|
||||||
|
Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
@@ -78,16 +81,29 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
end
|
end
|
||||||
|
|
||||||
def contact_inbox
|
def contact_inbox
|
||||||
|
@contact_inbox = build_contact_inbox
|
||||||
|
|
||||||
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
|
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_contact_inbox
|
||||||
|
return if params[:contact_id].blank? || params[:inbox_id].blank?
|
||||||
|
|
||||||
|
ContactInboxBuilder.new(
|
||||||
|
contact_id: params[:contact_id],
|
||||||
|
inbox_id: params[:inbox_id],
|
||||||
|
source_id: params[:source_id]
|
||||||
|
).perform
|
||||||
|
end
|
||||||
|
|
||||||
def conversation_params
|
def conversation_params
|
||||||
|
additional_attributes = params[:additional_attributes]&.permit! || {}
|
||||||
{
|
{
|
||||||
account_id: Current.account.id,
|
account_id: Current.account.id,
|
||||||
inbox_id: @contact_inbox.inbox_id,
|
inbox_id: @contact_inbox.inbox_id,
|
||||||
contact_id: @contact_inbox.contact_id,
|
contact_id: @contact_inbox.contact_id,
|
||||||
contact_inbox_id: @contact_inbox.id,
|
contact_inbox_id: @contact_inbox.id,
|
||||||
additional_attributes: params[:additional_attributes]
|
additional_attributes: additional_attributes
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ class Contact < ApplicationRecord
|
|||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false }
|
validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false }
|
||||||
validates :identifier, allow_blank: true, uniqueness: { scope: [:account_id] }
|
validates :identifier, allow_blank: true, uniqueness: { scope: [:account_id] }
|
||||||
validates :phone_number, allow_blank: true, uniqueness: { scope: [:account_id] }
|
validates :phone_number,
|
||||||
|
allow_blank: true, uniqueness: { scope: [:account_id] },
|
||||||
|
format: { with: /\+[1-9]\d{1,14}\z/, message: 'should be in e164 format' }
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
has_many :conversations, dependent: :destroy
|
has_many :conversations, dependent: :destroy
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ class ContactInbox < ApplicationRecord
|
|||||||
|
|
||||||
def validate_twilio_source_id
|
def validate_twilio_source_id
|
||||||
# https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164
|
# https://www.twilio.com/docs/glossary/what-e164#regex-matching-for-e164
|
||||||
if inbox.channel.medium == 'sms' && !/^\+[1-9]\d{1,14}$/.match?(source_id)
|
if inbox.channel.medium == 'sms' && !/\+[1-9]\d{1,14}\z/.match?(source_id)
|
||||||
errors.add(:source_id, 'invalid source id for twilio sms inbox. valid Regex /^\+[1-9]\d{1,14}$/')
|
errors.add(:source_id, 'invalid source id for twilio sms inbox. valid Regex /\+[1-9]\d{1,14}\z/')
|
||||||
elsif inbox.channel.medium == 'whatsapp' && !/^whatsapp:\+[1-9]\d{1,14}$/.match?(source_id)
|
elsif inbox.channel.medium == 'whatsapp' && !/whatsapp:\+[1-9]\d{1,14}\z/.match?(source_id)
|
||||||
errors.add(:source_id, 'invalid source id for twilio whatsapp inbox. valid Regex /^whatsapp:\+[1-9]\d{1,14}$/')
|
errors.add(:source_id, 'invalid source id for twilio whatsapp inbox. valid Regex /whatsapp:\+[1-9]\d{1,14}\z/')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ class ContactPolicy < ApplicationPolicy
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contactable_inboxes?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def show?
|
def show?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|||||||
52
app/services/contacts/contactable_inboxes_service.rb
Normal file
52
app/services/contacts/contactable_inboxes_service.rb
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
class Contacts::ContactableInboxesService
|
||||||
|
pattr_initialize [:contact!]
|
||||||
|
|
||||||
|
def get
|
||||||
|
account = contact.account
|
||||||
|
account.inboxes.map { |inbox| get_contactable_inbox(inbox) }.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def get_contactable_inbox(inbox)
|
||||||
|
return twilio_contactable_inbox(inbox) if inbox.channel_type == 'Channel::TwilioSms'
|
||||||
|
return email_contactable_inbox(inbox) if inbox.channel_type == 'Channel::Email'
|
||||||
|
return api_contactable_inbox(inbox) if inbox.channel_type == 'Channel::Api'
|
||||||
|
return website_contactable_inbox(inbox) if inbox.channel_type == 'Channel::WebWidget'
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def website_contactable_inbox(inbox)
|
||||||
|
latest_contact_inbox = inbox.contact_inboxes.where(contact: @contact).last
|
||||||
|
return unless latest_contact_inbox
|
||||||
|
# FIXME : change this when multiple conversations comes in
|
||||||
|
return if latest_contact_inbox.conversations.present?
|
||||||
|
|
||||||
|
{ source_id: latest_contact_inbox.source_id, inbox: inbox }
|
||||||
|
end
|
||||||
|
|
||||||
|
def api_contactable_inbox(inbox)
|
||||||
|
latest_contact_inbox = inbox.contact_inboxes.where(contact: @contact).last
|
||||||
|
source_id = latest_contact_inbox&.source_id || SecureRandom.uuid
|
||||||
|
|
||||||
|
{ source_id: source_id, inbox: inbox }
|
||||||
|
end
|
||||||
|
|
||||||
|
def email_contactable_inbox(inbox)
|
||||||
|
return unless @contact.email
|
||||||
|
|
||||||
|
{ source_id: @contact.email, inbox: inbox }
|
||||||
|
end
|
||||||
|
|
||||||
|
def twilio_contactable_inbox(inbox)
|
||||||
|
return unless @contact.phone_number
|
||||||
|
|
||||||
|
case inbox.channel.medium
|
||||||
|
when 'sms'
|
||||||
|
{ source_id: @contact.phone_number, inbox: inbox }
|
||||||
|
when 'whatsapp'
|
||||||
|
{ source_id: "whatsapp:#{@contact.phone_number}", inbox: inbox }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
json.payload do
|
||||||
|
json.array! @contactable_inboxes do |contactable_inbox|
|
||||||
|
json.inbox do
|
||||||
|
json.partial! 'api/v1/models/inbox.json.jbuilder', resource: contactable_inbox[:inbox]
|
||||||
|
end
|
||||||
|
json.source_id contactable_inbox[:source_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -69,6 +69,9 @@ Rails.application.routes.draw do
|
|||||||
get :search
|
get :search
|
||||||
post :import
|
post :import
|
||||||
end
|
end
|
||||||
|
member do
|
||||||
|
get :contactable_inboxes
|
||||||
|
end
|
||||||
scope module: :contacts do
|
scope module: :contacts do
|
||||||
resources :conversations, only: [:index]
|
resources :conversations, only: [:index]
|
||||||
resources :contact_inboxes, only: [:create]
|
resources :contact_inboxes, only: [:create]
|
||||||
|
|||||||
231
spec/builders/contact_inbox_builder_spec.rb
Normal file
231
spec/builders/contact_inbox_builder_spec.rb
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ::ContactInboxBuilder do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:contact) { create(:contact, account: account) }
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
describe 'twilio sms inbox' do
|
||||||
|
let!(:twilio_sms) { create(:channel_twilio_sms, account: account) }
|
||||||
|
let!(:twilio_inbox) { create(:inbox, channel: twilio_sms, account: account) }
|
||||||
|
|
||||||
|
it 'does not create contact inbox when contact inbox already exists with the source id provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: twilio_inbox, source_id: contact.phone_number)
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id,
|
||||||
|
source_id: contact.phone_number
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).to be(existing_contact_inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: twilio_inbox, source_id: contact.phone_number)
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).to be(existing_contact_inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new contact inbox when different source id is provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: twilio_inbox, source_id: contact.phone_number)
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id,
|
||||||
|
source_id: '+224213223422'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).not_to be(existing_contact_inbox.id)
|
||||||
|
expect(contact_inbox.source_id).not_to be('+224213223422')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.source_id).not_to be(contact.phone_number)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'twilio whatsapp inbox' do
|
||||||
|
let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) }
|
||||||
|
let!(:twilio_inbox) { create(:inbox, channel: twilio_whatsapp, account: account) }
|
||||||
|
|
||||||
|
it 'does not create contact inbox when contact inbox already exists with the source id provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: twilio_inbox, source_id: "whatsapp:#{contact.phone_number}")
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id,
|
||||||
|
source_id: "whatsapp:#{contact.phone_number}"
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).to be(existing_contact_inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create contact inbox when contact inbox already exists with phone number and source id is not provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: twilio_inbox, source_id: "whatsapp:#{contact.phone_number}")
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).to be(existing_contact_inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new contact inbox when different source id is provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: twilio_inbox, source_id: "whatsapp:#{contact.phone_number}")
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id,
|
||||||
|
source_id: 'whatsapp:+555555'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).not_to be(existing_contact_inbox.id)
|
||||||
|
expect(contact_inbox.source_id).not_to be('whatsapp:+55555')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a contact inbox with contact phone number when source id not provided and no contact inbox exists' do
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twilio_inbox.id
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.source_id).not_to be("whatsapp:#{contact.phone_number}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'email inbox' do
|
||||||
|
let!(:email_channel) { create(:channel_email, account: account) }
|
||||||
|
let!(:email_inbox) { create(:inbox, channel: email_channel, account: account) }
|
||||||
|
|
||||||
|
it 'does not create contact inbox when contact inbox already exists with the source id provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: email_inbox, source_id: contact.email)
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: email_inbox.id,
|
||||||
|
source_id: contact.email
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).to be(existing_contact_inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create contact inbox when contact inbox already exists with email and source id is not provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: email_inbox, source_id: contact.email)
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: email_inbox.id
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).to be(existing_contact_inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new contact inbox when different source id is provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: email_inbox, source_id: contact.email)
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: email_inbox.id,
|
||||||
|
source_id: 'xyc@xyc.com'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).not_to be(existing_contact_inbox.id)
|
||||||
|
expect(contact_inbox.source_id).not_to be('xyc@xyc.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a contact inbox with contact email when source id not provided and no contact inbox exists' do
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: email_inbox.id
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.source_id).not_to be(contact.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'api inbox' do
|
||||||
|
let!(:api_channel) { create(:channel_api, account: account) }
|
||||||
|
let!(:api_inbox) { create(:inbox, channel: api_channel, account: account) }
|
||||||
|
|
||||||
|
it 'does not create contact inbox when contact inbox already exists with the source id provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: api_inbox, source_id: 'test')
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: api_inbox.id,
|
||||||
|
source_id: 'test'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).to be(existing_contact_inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new contact inbox when different source id is provided' do
|
||||||
|
existing_contact_inbox = create(:contact_inbox, contact: contact, inbox: api_inbox, source_id: SecureRandom.uuid)
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: api_inbox.id,
|
||||||
|
source_id: 'test'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.id).not_to be(existing_contact_inbox.id)
|
||||||
|
expect(contact_inbox.source_id).not_to be('test')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a contact inbox with SecureRandom.uuid when source id not provided and no contact inbox exists' do
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: api_inbox.id
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox.source_id).not_to be(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'web widget' do
|
||||||
|
let!(:website_channel) { create(:channel_widget, account: account) }
|
||||||
|
let!(:website_inbox) { create(:inbox, channel: website_channel, account: account) }
|
||||||
|
|
||||||
|
it 'does not create contact inbox' do
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: website_inbox.id,
|
||||||
|
source_id: 'test'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox).to be(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'facebook inbox' do
|
||||||
|
let!(:facebook_channel) { create(:channel_facebook_page, account: account) }
|
||||||
|
let!(:facebook_inbox) { create(:inbox, channel: facebook_channel, account: account) }
|
||||||
|
|
||||||
|
it 'does not create contact inbox' do
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: facebook_inbox.id,
|
||||||
|
source_id: 'test'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox).to be(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'twitter inbox' do
|
||||||
|
let!(:twitter_channel) { create(:channel_twitter_profile, account: account) }
|
||||||
|
let!(:twitter_inbox) { create(:inbox, channel: twitter_channel, account: account) }
|
||||||
|
|
||||||
|
it 'does not create contact inbox' do
|
||||||
|
contact_inbox = described_class.new(
|
||||||
|
contact_id: contact.id,
|
||||||
|
inbox_id: twitter_inbox.id,
|
||||||
|
source_id: 'test'
|
||||||
|
).perform
|
||||||
|
|
||||||
|
expect(contact_inbox).to be(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -189,6 +189,34 @@ RSpec.describe 'Contacts API', type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/contacts/:id/contactable_inboxes' do
|
||||||
|
let!(:contact) { create(:contact, account: account) }
|
||||||
|
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/contactable_inboxes"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
it 'shows the contact' do
|
||||||
|
inbox_service = double
|
||||||
|
allow(Contacts::ContactableInboxesService).to receive(:new).and_return(inbox_service)
|
||||||
|
allow(inbox_service).to receive(:get).and_return({})
|
||||||
|
expect(inbox_service).to receive(:get).and_return({})
|
||||||
|
get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/contactable_inboxes",
|
||||||
|
headers: admin.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST /api/v1/accounts/{account.id}/contacts' do
|
describe 'POST /api/v1/accounts/{account.id}/contacts' do
|
||||||
let(:custom_attributes) { { test: 'test', test1: 'test1' } }
|
let(:custom_attributes) { { test: 'test', test1: 'test1' } }
|
||||||
let(:valid_params) { { contact: { name: 'test', custom_attributes: custom_attributes } } }
|
let(:valid_params) { { contact: { name: 'test', custom_attributes: custom_attributes } } }
|
||||||
|
|||||||
@@ -151,14 +151,43 @@ RSpec.describe 'Conversations API', type: :request do
|
|||||||
|
|
||||||
it 'creates a new conversation' do
|
it 'creates a new conversation' do
|
||||||
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||||
|
additional_attributes = { test: 'test' }
|
||||||
post "/api/v1/accounts/#{account.id}/conversations",
|
post "/api/v1/accounts/#{account.id}/conversations",
|
||||||
headers: agent.create_new_auth_token,
|
headers: agent.create_new_auth_token,
|
||||||
params: { source_id: contact_inbox.source_id },
|
params: { source_id: contact_inbox.source_id, additional_attributes: additional_attributes },
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
||||||
|
expect(response_data[:additional_attributes]).to eq(additional_attributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new conversation with message when message is passed' do
|
||||||
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||||
|
post "/api/v1/accounts/#{account.id}/conversations",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
params: { source_id: contact_inbox.source_id, message: { content: 'hi' } },
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
response_data = JSON.parse(response.body, symbolize_names: true)
|
response_data = JSON.parse(response.body, symbolize_names: true)
|
||||||
expect(response_data[:additional_attributes]).to eq({})
|
expect(response_data[:additional_attributes]).to eq({})
|
||||||
|
expect(account.conversations.find_by(display_id: response_data[:id]).messages.first.content).to eq 'hi'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls contact inbox builder if contact_id and inbox_id is present' do
|
||||||
|
builder = double
|
||||||
|
contact = create(:contact, account: account)
|
||||||
|
inbox = create(:inbox, account: account)
|
||||||
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||||
|
allow(ContactInboxBuilder).to receive(:new).and_return(builder)
|
||||||
|
allow(builder).to receive(:perform)
|
||||||
|
expect(builder).to receive(:perform)
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/conversations",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
params: { contact_id: contact.id, inbox_id: inbox.id },
|
||||||
|
as: :json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
66
spec/services/contacts/contactable_inboxes_service_spec.rb
Normal file
66
spec/services/contacts/contactable_inboxes_service_spec.rb
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Contacts::ContactableInboxesService do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:contact) { create(:contact, account: account) }
|
||||||
|
let!(:twilio_sms) { create(:channel_twilio_sms, account: account) }
|
||||||
|
let!(:twilio_sms_inbox) { create(:inbox, channel: twilio_sms, account: account) }
|
||||||
|
let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) }
|
||||||
|
let!(:twilio_whatsapp_inbox) { create(:inbox, channel: twilio_whatsapp, account: account) }
|
||||||
|
let!(:email_channel) { create(:channel_email, account: account) }
|
||||||
|
let!(:email_inbox) { create(:inbox, channel: email_channel, account: account) }
|
||||||
|
let!(:api_channel) { create(:channel_api, account: account) }
|
||||||
|
let!(:api_inbox) { create(:inbox, channel: api_channel, account: account) }
|
||||||
|
let!(:website_channel) { create(:channel_widget, account: account) }
|
||||||
|
let!(:website_inbox) { create(:inbox, channel: website_channel, account: account) }
|
||||||
|
|
||||||
|
describe '#get' do
|
||||||
|
it 'returns the contactable inboxes for the contact' do
|
||||||
|
contactable_inboxes = described_class.new(contact: contact).get
|
||||||
|
|
||||||
|
expect(contactable_inboxes).to include({ source_id: contact.phone_number, inbox: twilio_sms_inbox })
|
||||||
|
expect(contactable_inboxes).to include({ source_id: "whatsapp:#{contact.phone_number}", inbox: twilio_whatsapp_inbox })
|
||||||
|
expect(contactable_inboxes).to include({ source_id: contact.email, inbox: email_inbox })
|
||||||
|
expect(contactable_inboxes.pluck(:inbox)).to include(api_inbox)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'doest not return the non contactable inboxes for the contact' do
|
||||||
|
facebook_channel = create(:channel_facebook_page, account: account)
|
||||||
|
facebook_inbox = create(:inbox, channel: facebook_channel, account: account)
|
||||||
|
twitter_channel = create(:channel_twitter_profile, account: account)
|
||||||
|
twitter_inbox = create(:inbox, channel: twitter_channel, account: account)
|
||||||
|
|
||||||
|
contactable_inboxes = described_class.new(contact: contact).get
|
||||||
|
|
||||||
|
expect(contactable_inboxes.pluck(:inbox)).not_to include(website_inbox)
|
||||||
|
expect(contactable_inboxes.pluck(:inbox)).not_to include(facebook_inbox)
|
||||||
|
expect(contactable_inboxes.pluck(:inbox)).not_to include(twitter_inbox)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when api inbox is available' do
|
||||||
|
it 'returns existing source id if contact inbox exists' do
|
||||||
|
contact_inbox = create(:contact_inbox, inbox: api_inbox, contact: contact)
|
||||||
|
|
||||||
|
contactable_inboxes = described_class.new(contact: contact).get
|
||||||
|
expect(contactable_inboxes).to include({ source_id: contact_inbox.source_id, inbox: api_inbox })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when website inbox is available' do
|
||||||
|
it 'returns existing source id if contact inbox exists without any conversations' do
|
||||||
|
contact_inbox = create(:contact_inbox, inbox: website_inbox, contact: contact)
|
||||||
|
|
||||||
|
contactable_inboxes = described_class.new(contact: contact).get
|
||||||
|
expect(contactable_inboxes).to include({ source_id: contact_inbox.source_id, inbox: website_inbox })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return existing source id if contact inbox exists with conversations' do
|
||||||
|
contact_inbox = create(:contact_inbox, inbox: website_inbox, contact: contact)
|
||||||
|
create(:conversation, contact: contact, inbox: website_inbox, contact_inbox: contact_inbox)
|
||||||
|
|
||||||
|
contactable_inboxes = described_class.new(contact: contact).get
|
||||||
|
expect(contactable_inboxes.pluck(:inbox)).not_to include(website_inbox)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user