Feature: API Channel (#1052)
This commit is contained in:
parent
fa04098c20
commit
8079bf50a0
40 changed files with 735 additions and 246 deletions
139
app/builders/messages/facebook/message_builder.rb
Normal file
139
app/builders/messages/facebook/message_builder.rb
Normal file
|
@ -0,0 +1,139 @@
|
|||
# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
|
||||
# Assumptions
|
||||
# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
|
||||
# based on this we are showing "not sent from chatwoot" message in frontend
|
||||
# Hence there is no need to set user_id in message for outgoing echo messages.
|
||||
|
||||
class Messages::Facebook::MessageBuilder
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, inbox, outgoing_echo = false)
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@sender_id = (outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (outgoing_echo ? :outgoing : :incoming)
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
|
||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
|
||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = conversation.messages.create!(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||
attachment_obj.save!
|
||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||
end
|
||||
end
|
||||
|
||||
def attach_file(attachment, file_url)
|
||||
file_resource = LocalResource.new(file_url)
|
||||
attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding)
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
))
|
||||
end
|
||||
|
||||
def attachment_params(attachment)
|
||||
file_type = attachment['type'].to_sym
|
||||
params = { file_type: file_type, account_id: @message.account_id }
|
||||
|
||||
if [:image, :file, :audio, :video].include? file_type
|
||||
params.merge!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
source_id: response.identifier,
|
||||
sender: contact
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue StandardError => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,2 +0,0 @@
|
|||
class Messages::IncomingMessageBuilder < Messages::MessageBuilder
|
||||
end
|
|
@ -1,139 +1,57 @@
|
|||
# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
|
||||
# Assumptions
|
||||
# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
|
||||
# based on this we are showing "not sent from chatwoot" message in frontend
|
||||
# Hence there is no need to set user_id in message for outgoing echo messages.
|
||||
|
||||
class Messages::MessageBuilder
|
||||
attr_reader :response
|
||||
include ::FileTypeHelper
|
||||
attr_reader :message
|
||||
|
||||
def initialize(response, inbox, outgoing_echo = false)
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@sender_id = (outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (outgoing_echo ? :outgoing : :incoming)
|
||||
def initialize(user, conversation, params)
|
||||
@content = params[:content]
|
||||
@private = params[:private] || false
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@message_type = params[:message_type] || 'outgoing'
|
||||
@content_type = params[:content_type]
|
||||
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
||||
@attachments = params[:attachments]
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
@message = @conversation.messages.build(message_params)
|
||||
if @attachments.present?
|
||||
@attachments.each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type)
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
@message.save
|
||||
@message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
|
||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
|
||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = conversation.messages.create!(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||
attachment_obj.save!
|
||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||
end
|
||||
end
|
||||
|
||||
def attach_file(attachment, file_url)
|
||||
file_resource = LocalResource.new(file_url)
|
||||
attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding)
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
))
|
||||
end
|
||||
|
||||
def attachment_params(attachment)
|
||||
file_type = attachment['type'].to_sym
|
||||
params = { file_type: file_type, account_id: @message.account_id }
|
||||
|
||||
if [:image, :file, :audio, :video].include? file_type
|
||||
params.merge!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
def message_type
|
||||
if @conversation.inbox.channel.class != Channel::Api && @message_type == 'incoming'
|
||||
raise StandardError, 'Incoming messages are only allowed in Api inboxes'
|
||||
end
|
||||
|
||||
params
|
||||
@message_type
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
def sender
|
||||
message_type == 'outgoing' ? @user : @conversation.contact
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
source_id: response.identifier,
|
||||
sender: contact
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue Exception => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: message_type,
|
||||
content: @content,
|
||||
private: @private,
|
||||
sender: sender,
|
||||
content_type: @content_type,
|
||||
items: @items
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
class Messages::Outgoing::EchoBuilder < ::Messages::MessageBuilder
|
||||
end
|
|
@ -1,46 +0,0 @@
|
|||
class Messages::Outgoing::NormalBuilder
|
||||
include ::FileTypeHelper
|
||||
attr_reader :message
|
||||
|
||||
def initialize(user, conversation, params)
|
||||
@content = params[:content]
|
||||
@private = params[:private] || false
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@fb_id = params[:fb_id]
|
||||
@content_type = params[:content_type]
|
||||
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
||||
@attachments = params[:attachments]
|
||||
end
|
||||
|
||||
def perform
|
||||
@message = @conversation.messages.build(message_params)
|
||||
if @attachments.present?
|
||||
@attachments.each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type)
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
end
|
||||
@message.save
|
||||
@message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: :outgoing,
|
||||
content: @content,
|
||||
private: @private,
|
||||
sender: @user,
|
||||
source_id: @fb_id,
|
||||
content_type: @content_type,
|
||||
items: @items
|
||||
}
|
||||
end
|
||||
end
|
|
@ -11,9 +11,11 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
|||
def show; end
|
||||
|
||||
def create
|
||||
@contact = Current.account.contacts.new(contact_create_params)
|
||||
@contact.save!
|
||||
render json: @contact
|
||||
ActiveRecord::Base.transaction do
|
||||
@contact = Current.account.contacts.new(contact_create_params)
|
||||
@contact.save!
|
||||
@contact_inbox = build_contact_inbox
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -26,6 +28,14 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
|||
authorize(Contact)
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
return if params[:inbox_id].blank?
|
||||
|
||||
inbox = Inbox.find(params[:inbox_id])
|
||||
source_id = params[:source_id] || SecureRandom.uuid
|
||||
ContactInbox.create(contact: @contact, inbox: inbox, source_id: source_id)
|
||||
end
|
||||
|
||||
def contact_params
|
||||
params.require(:contact).permit(:name, :email, :phone_number)
|
||||
end
|
||||
|
|
|
@ -5,8 +5,10 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
|||
|
||||
def create
|
||||
user = current_user || @resource
|
||||
mb = Messages::Outgoing::NormalBuilder.new(user, @conversation, params)
|
||||
mb = Messages::MessageBuilder.new(user, @conversation, params)
|
||||
@message = mb.perform
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -4,12 +4,12 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@inboxes = policy_scope(Current.account.inboxes)
|
||||
@inboxes = policy_scope(Current.account.inboxes.includes(:channel, :avatar_attachment))
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
channel = web_widgets.create!(permitted_params[:channel].except(:type)) if permitted_params[:channel][:type] == 'web_widget'
|
||||
channel = create_channel
|
||||
@inbox = Current.account.inboxes.build(
|
||||
name: permitted_params[:name],
|
||||
greeting_message: permitted_params[:greeting_message],
|
||||
|
@ -52,21 +52,28 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||
@agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot]
|
||||
end
|
||||
|
||||
def web_widgets
|
||||
Current.account.web_widgets
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(Inbox)
|
||||
end
|
||||
|
||||
def create_channel
|
||||
case permitted_params[:channel][:type]
|
||||
when 'web_widget'
|
||||
Current.account.web_widgets.create!(permitted_params[:channel].except(:type))
|
||||
when 'api'
|
||||
Current.account.api_channels.create!(permitted_params[:channel].except(:type))
|
||||
when 'email'
|
||||
Current.account.email_channels.create!(permitted_params[:channel].except(:type))
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, channel:
|
||||
[:type, :website_url, :widget_color, :welcome_title, :welcome_tagline])
|
||||
[:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email])
|
||||
end
|
||||
|
||||
def inbox_update_params
|
||||
params.permit(:enable_auto_assignment, :name, :avatar, :greeting_message, :greeting_enabled,
|
||||
channel: [:website_url, :widget_color, :welcome_title, :welcome_tagline])
|
||||
channel: [:website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email])
|
||||
end
|
||||
end
|
||||
|
|
BIN
app/javascript/dashboard/assets/images/channels/api.png
Normal file
BIN
app/javascript/dashboard/assets/images/channels/api.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
app/javascript/dashboard/assets/images/channels/email.png
Normal file
BIN
app/javascript/dashboard/assets/images/channels/email.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
|
@ -63,6 +63,8 @@ const INBOX_TYPES = {
|
|||
FB: 'Channel::FacebookPage',
|
||||
TWITTER: 'Channel::TwitterProfile',
|
||||
TWILIO: 'Channel::TwilioSms',
|
||||
API: 'Channel::Api',
|
||||
EMAIL: 'Channel::Email',
|
||||
};
|
||||
const getInboxClassByType = type => {
|
||||
switch (type) {
|
||||
|
@ -78,6 +80,12 @@ const getInboxClassByType = type => {
|
|||
case INBOX_TYPES.TWILIO:
|
||||
return 'ion-android-textsms';
|
||||
|
||||
case INBOX_TYPES.API:
|
||||
return 'ion-cloud';
|
||||
|
||||
case INBOX_TYPES.EMAIL:
|
||||
return 'ion-email';
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -16,6 +16,14 @@
|
|||
v-if="channel === 'telegram'"
|
||||
src="~dashboard/assets/images/channels/telegram.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel === 'api'"
|
||||
src="~dashboard/assets/images/channels/api.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel === 'email'"
|
||||
src="~dashboard/assets/images/channels/email.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel === 'line'"
|
||||
src="~dashboard/assets/images/channels/line.png"
|
||||
|
@ -56,7 +64,10 @@ export default {
|
|||
if (channel === 'twitter') {
|
||||
return this.enabledFeatures.channel_twitter;
|
||||
}
|
||||
return ['website', 'twilio'].includes(channel);
|
||||
if (channel === 'email') {
|
||||
return this.enabledFeatures.channel_email;
|
||||
}
|
||||
return ['website', 'twilio', 'api'].includes(channel);
|
||||
},
|
||||
onItemClick() {
|
||||
if (this.isActive(this.channel)) {
|
||||
|
|
|
@ -115,6 +115,43 @@
|
|||
"ERROR_MESSAGE": "We were not able to authenticate Twilio credentials, please try again"
|
||||
}
|
||||
},
|
||||
"API_CHANNEL": {
|
||||
"TITLE": "API Channel",
|
||||
"DESC": "Integrate with API channel and start supporting your customers via chatwoot.",
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "Channel Name",
|
||||
"PLACEHOLDER": "Please enter a channel name",
|
||||
"ERROR": "This field is required"
|
||||
},
|
||||
"WEBHOOK_URL": {
|
||||
"LABEL": "Webhook Url",
|
||||
"SUBTITLE": "Configure the url where you want to recieve callbacks from chatwoot on events.",
|
||||
"PLACEHOLDER": "Webhook Url"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create API Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the api channel"
|
||||
}
|
||||
},
|
||||
"EMAIL_CHANNEL": {
|
||||
"TITLE": "Email Channel",
|
||||
"DESC": "Integrate you email inbox with chatwoot.",
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "Channel Name",
|
||||
"PLACEHOLDER": "Please enter a channel name",
|
||||
"ERROR": "This field is required"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "Email",
|
||||
"SUBTITLE": "Email where your customers sends you support tickets",
|
||||
"PLACEHOLDER": "Email"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create Email Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the email channel"
|
||||
},
|
||||
"FINISH_MESSAGE" : "Start forwarding your emails to the following email address."
|
||||
},
|
||||
"AUTH": {
|
||||
"TITLE": "Channels",
|
||||
"DESC": "Currently we support Website live chat widgets, Facebook Pages and Twitter profiles as platforms. We have more platforms like Whatsapp, Email, Telegram and Line in the works, which will be out soon."
|
||||
|
|
|
@ -34,6 +34,8 @@ export default {
|
|||
'facebook',
|
||||
'twitter',
|
||||
'twilio',
|
||||
'email',
|
||||
'api',
|
||||
'telegram',
|
||||
'line',
|
||||
],
|
||||
|
|
|
@ -21,6 +21,14 @@
|
|||
>
|
||||
</woot-code>
|
||||
</div>
|
||||
<div class="medium-6 small-offset-3">
|
||||
<woot-code
|
||||
v-if="isAEmailInbox"
|
||||
lang="html"
|
||||
:script="currentInbox.forward_to_address"
|
||||
>
|
||||
</woot-code>
|
||||
</div>
|
||||
<router-link
|
||||
class="button success nice"
|
||||
:to="{
|
||||
|
@ -53,6 +61,9 @@ export default {
|
|||
isATwilioInbox() {
|
||||
return this.currentInbox.channel_type === 'Channel::TwilioSms';
|
||||
},
|
||||
isAEmailInbox() {
|
||||
return this.currentInbox.channel_type === 'Channel::Email';
|
||||
},
|
||||
message() {
|
||||
if (this.isATwilioInbox) {
|
||||
return `${this.$t('INBOX_MGMT.FINISH.MESSAGE')}. ${this.$t(
|
||||
|
@ -60,6 +71,10 @@ export default {
|
|||
)}`;
|
||||
}
|
||||
|
||||
if (this.isAEmailInbox) {
|
||||
return this.$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.FINISH_MESSAGE');
|
||||
}
|
||||
|
||||
if (!this.currentInbox.website_token) {
|
||||
return this.$t('INBOX_MGMT.FINISH.MESSAGE');
|
||||
}
|
||||
|
|
|
@ -45,6 +45,12 @@
|
|||
<span v-if="item.channel_type === 'Channel::TwilioSms'">
|
||||
Twilio SMS
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Email'">
|
||||
Email
|
||||
</span>
|
||||
<span v-if="item.channel_type === 'Channel::Api'">
|
||||
Api
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
|
|
|
@ -2,12 +2,16 @@ import Facebook from './channels/Facebook';
|
|||
import Website from './channels/Website';
|
||||
import Twitter from './channels/Twitter';
|
||||
import Twilio from './channels/Twilio';
|
||||
import Api from './channels/Api';
|
||||
import Email from './channels/Email';
|
||||
|
||||
const channelViewList = {
|
||||
facebook: Facebook,
|
||||
website: Website,
|
||||
twitter: Twitter,
|
||||
twilio: Twilio,
|
||||
api: Api,
|
||||
email: Email,
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div class="wizard-body small-9 columns">
|
||||
<page-header
|
||||
:header-title="$t('INBOX_MGMT.ADD.API_CHANNEL.TITLE')"
|
||||
:header-content="$t('INBOX_MGMT.ADD.API_CHANNEL.DESC')"
|
||||
/>
|
||||
<form class="row" @submit.prevent="createChannel()">
|
||||
<div class="medium-8 columns">
|
||||
<label :class="{ error: $v.channelName.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.API_CHANNEL.CHANNEL_NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="channelName"
|
||||
type="text"
|
||||
:placeholder="
|
||||
$t('INBOX_MGMT.ADD.API_CHANNEL.CHANNEL_NAME.PLACEHOLDER')
|
||||
"
|
||||
@blur="$v.channelName.$touch"
|
||||
/>
|
||||
<span v-if="$v.channelName.$error" class="message">{{
|
||||
$t('INBOX_MGMT.ADD.API_CHANNEL.CHANNEL_NAME.ERROR')
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="medium-8 columns">
|
||||
<label :class="{ error: $v.webhookUrl.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.API_CHANNEL.WEBHOOK_URL.LABEL') }}
|
||||
<input
|
||||
v-model.trim="webhookUrl"
|
||||
type="text"
|
||||
:placeholder="
|
||||
$t('INBOX_MGMT.ADD.API_CHANNEL.WEBHOOK_URL.PLACEHOLDER')
|
||||
"
|
||||
@blur="$v.webhookUrl.$touch"
|
||||
/>
|
||||
</label>
|
||||
<p class="help-text">
|
||||
{{ $t('INBOX_MGMT.ADD.API_CHANNEL.WEBHOOK_URL.SUBTITLE') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="medium-12 columns">
|
||||
<woot-submit-button
|
||||
:loading="uiFlags.isCreating"
|
||||
:button-text="$t('INBOX_MGMT.ADD.API_CHANNEL.SUBMIT_BUTTON')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import router from '../../../../index';
|
||||
import PageHeader from '../../SettingsSubPageHeader';
|
||||
|
||||
const shouldBeWebhookUrl = (value = '') => value.startsWith('http');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageHeader,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
channelName: '',
|
||||
webhookUrl: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'inboxes/getUIFlags',
|
||||
}),
|
||||
},
|
||||
validations: {
|
||||
channelName: { required },
|
||||
webhookUrl: { required, shouldBeWebhookUrl },
|
||||
},
|
||||
methods: {
|
||||
async createChannel() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const apiChannel = await this.$store.dispatch('inboxes/createChannel', {
|
||||
name: this.channelName,
|
||||
channel: {
|
||||
type: 'api',
|
||||
webhook_url: this.webhookUrl,
|
||||
},
|
||||
});
|
||||
|
||||
router.replace({
|
||||
name: 'settings_inboxes_add_agents',
|
||||
params: {
|
||||
page: 'new',
|
||||
inbox_id: apiChannel.id,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('INBOX_MGMT.ADD.API_CHANNEL.API.ERROR_MESSAGE'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div class="wizard-body small-9 columns">
|
||||
<page-header
|
||||
:header-title="$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.TITLE')"
|
||||
:header-content="$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.DESC')"
|
||||
/>
|
||||
<form class="row" @submit.prevent="createChannel()">
|
||||
<div class="medium-8 columns">
|
||||
<label :class="{ error: $v.channelName.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.EMAIL_CHANNEL.CHANNEL_NAME.LABEL') }}
|
||||
<input
|
||||
v-model.trim="channelName"
|
||||
type="text"
|
||||
:placeholder="
|
||||
$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.CHANNEL_NAME.PLACEHOLDER')
|
||||
"
|
||||
@blur="$v.channelName.$touch"
|
||||
/>
|
||||
<span v-if="$v.channelName.$error" class="message">{{
|
||||
$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.CHANNEL_NAME.ERROR')
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="medium-8 columns">
|
||||
<label :class="{ error: $v.email.$error }">
|
||||
{{ $t('INBOX_MGMT.ADD.EMAIL_CHANNEL.EMAIL.LABEL') }}
|
||||
<input
|
||||
v-model.trim="email"
|
||||
type="text"
|
||||
:placeholder="$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.EMAIL.PLACEHOLDER')"
|
||||
@blur="$v.email.$touch"
|
||||
/>
|
||||
</label>
|
||||
<p class="help-text">
|
||||
{{ $t('INBOX_MGMT.ADD.EMAIL_CHANNEL.EMAIL.SUBTITLE') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="medium-12 columns">
|
||||
<woot-submit-button
|
||||
:loading="uiFlags.isCreating"
|
||||
:button-text="$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.SUBMIT_BUTTON')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import router from '../../../../index';
|
||||
import PageHeader from '../../SettingsSubPageHeader';
|
||||
|
||||
const validEmail = (value = '') => value.includes('@');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageHeader,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
data() {
|
||||
return {
|
||||
channelName: '',
|
||||
email: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'inboxes/getUIFlags',
|
||||
}),
|
||||
},
|
||||
validations: {
|
||||
channelName: { required },
|
||||
email: { required, validEmail },
|
||||
},
|
||||
methods: {
|
||||
async createChannel() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const emailChannel = await this.$store.dispatch(
|
||||
'inboxes/createChannel',
|
||||
{
|
||||
name: this.channelName,
|
||||
channel: {
|
||||
type: 'email',
|
||||
email: this.email,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
router.replace({
|
||||
name: 'settings_inboxes_add_agents',
|
||||
params: {
|
||||
page: 'new',
|
||||
inbox_id: emailChannel.id,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.showAlert(
|
||||
this.$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.API.ERROR_MESSAGE')
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -55,6 +55,18 @@ export const actions = {
|
|||
commit(types.default.SET_INBOXES_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
createChannel: async ({ commit }, params) => {
|
||||
try {
|
||||
commit(types.default.SET_INBOXES_UI_FLAG, { isCreating: true });
|
||||
const response = await WebChannel.create(params);
|
||||
commit(types.default.ADD_INBOXES, response.data);
|
||||
commit(types.default.SET_INBOXES_UI_FLAG, { isCreating: false });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
commit(types.default.SET_INBOXES_UI_FLAG, { isCreating: false });
|
||||
throw new Error(error);
|
||||
}
|
||||
},
|
||||
createWebsiteChannel: async ({ commit }, params) => {
|
||||
try {
|
||||
commit(types.default.SET_INBOXES_UI_FLAG, { isCreating: true });
|
||||
|
|
|
@ -50,9 +50,7 @@ class WebhookListener < BaseListener
|
|||
WebhookJob.perform_later(webhook.url, payload)
|
||||
end
|
||||
|
||||
# Inbox webhooks
|
||||
inbox.webhooks.inbox.each do |webhook|
|
||||
WebhookJob.perform_later(webhook.url, payload)
|
||||
end
|
||||
# Deliver for API Inbox
|
||||
WebhookJob.perform_later(inbox.channel.webhook_url, payload) if inbox.channel_type == 'Channel::Api'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,6 +43,8 @@ class Account < ApplicationRecord
|
|||
has_many :twilio_sms, dependent: :destroy, class_name: '::Channel::TwilioSms'
|
||||
has_many :twitter_profiles, dependent: :destroy, class_name: '::Channel::TwitterProfile'
|
||||
has_many :web_widgets, dependent: :destroy, class_name: '::Channel::WebWidget'
|
||||
has_many :email_channels, dependent: :destroy, class_name: '::Channel::Email'
|
||||
has_many :api_channels, dependent: :destroy, class_name: '::Channel::Api'
|
||||
has_many :canned_responses, dependent: :destroy
|
||||
has_many :webhooks, dependent: :destroy
|
||||
has_many :labels, dependent: :destroy
|
||||
|
|
19
app/models/channel/api.rb
Normal file
19
app/models/channel/api.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: channel_api
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# webhook_url :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
#
|
||||
|
||||
class Channel::Api < ApplicationRecord
|
||||
self.table_name = 'channel_api'
|
||||
|
||||
validates :account_id, presence: true
|
||||
belongs_to :account
|
||||
|
||||
has_one :inbox, as: :channel, dependent: :destroy
|
||||
end
|
35
app/models/channel/email.rb
Normal file
35
app/models/channel/email.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: channel_email
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# email :string not null
|
||||
# forward_to_address :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_channel_email_on_email (email) UNIQUE
|
||||
# index_channel_email_on_forward_to_address (forward_to_address) UNIQUE
|
||||
#
|
||||
|
||||
class Channel::Email < ApplicationRecord
|
||||
self.table_name = 'channel_email'
|
||||
|
||||
validates :account_id, presence: true
|
||||
belongs_to :account
|
||||
validates :email, uniqueness: true
|
||||
validates :forward_to_address, uniqueness: true
|
||||
|
||||
has_one :inbox, as: :channel, dependent: :destroy
|
||||
before_validation :ensure_forward_to_address, on: :create
|
||||
|
||||
private
|
||||
|
||||
def ensure_forward_to_address
|
||||
# TODO : implement better logic here
|
||||
self.forward_to_address ||= "#{SecureRandom.hex}@xyc.com"
|
||||
end
|
||||
end
|
|
@ -17,9 +17,6 @@
|
|||
#
|
||||
|
||||
class Channel::FacebookPage < ApplicationRecord
|
||||
# FIXME: this should be removed post 1.4 release. we moved avatars to inbox
|
||||
include Avatarable
|
||||
|
||||
self.table_name = 'channel_facebook_pages'
|
||||
|
||||
validates :account_id, presence: true
|
||||
|
|
9
app/views/api/v1/accounts/contacts/create.json.jbuilder
Normal file
9
app/views/api/v1/accounts/contacts/create.json.jbuilder
Normal file
|
@ -0,0 +1,9 @@
|
|||
json.payload do
|
||||
json.contact do
|
||||
json.partial! 'api/v1/models/contact.json.jbuilder', resource: @contact
|
||||
end
|
||||
json.contact_inbox do
|
||||
json.inbox @contact_inbox&.inbox
|
||||
json.source_id @contact_inbox&.source_id
|
||||
end
|
||||
end
|
|
@ -1,14 +1 @@
|
|||
json.id @inbox.id
|
||||
json.channel_id @inbox.channel_id
|
||||
json.name @inbox.name
|
||||
json.channel_type @inbox.channel_type
|
||||
json.greeting_enabled @inbox.greeting_enabled
|
||||
json.greeting_message @inbox.greeting_message
|
||||
json.avatar_url @inbox.try(:avatar_url)
|
||||
json.website_token @inbox.channel.try(:website_token)
|
||||
json.widget_color @inbox.channel.try(:widget_color)
|
||||
json.website_url @inbox.channel.try(:website_url)
|
||||
json.welcome_title @inbox.channel.try(:welcome_title)
|
||||
json.welcome_tagline @inbox.channel.try(:welcome_tagline)
|
||||
json.web_widget_script @inbox.channel.try(:web_widget_script)
|
||||
json.enable_auto_assignment @inbox.enable_auto_assignment
|
||||
json.partial! 'api/v1/models/inbox.json.jbuilder', resource: @inbox
|
||||
|
|
|
@ -1,19 +1,5 @@
|
|||
json.payload do
|
||||
json.array! @inboxes do |inbox|
|
||||
json.id inbox.id
|
||||
json.channel_id inbox.channel_id
|
||||
json.name inbox.name
|
||||
json.channel_type inbox.channel_type
|
||||
json.greeting_enabled inbox.greeting_enabled
|
||||
json.greeting_message inbox.greeting_message
|
||||
json.avatar_url inbox.try(:avatar_url)
|
||||
json.page_id inbox.channel.try(:page_id)
|
||||
json.widget_color inbox.channel.try(:widget_color)
|
||||
json.website_url inbox.channel.try(:website_url)
|
||||
json.welcome_title inbox.channel.try(:welcome_title)
|
||||
json.welcome_tagline inbox.channel.try(:welcome_tagline)
|
||||
json.enable_auto_assignment inbox.enable_auto_assignment
|
||||
json.web_widget_script inbox.channel.try(:web_widget_script)
|
||||
json.phone_number inbox.channel.try(:phone_number)
|
||||
json.partial! 'api/v1/models/inbox.json.jbuilder', resource: inbox
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1 @@
|
|||
json.id @inbox.id
|
||||
json.channel_id @inbox.channel_id
|
||||
json.name @inbox.name
|
||||
json.channel_type @inbox.channel_type
|
||||
json.greeting_enabled @inbox.greeting_enabled
|
||||
json.greeting_message @inbox.greeting_message
|
||||
json.avatar_url @inbox.try(:avatar_url)
|
||||
json.website_token @inbox.channel.try(:website_token)
|
||||
json.widget_color @inbox.channel.try(:widget_color)
|
||||
json.website_url @inbox.channel.try(:website_url)
|
||||
json.welcome_title @inbox.channel.try(:welcome_title)
|
||||
json.welcome_tagline @inbox.channel.try(:welcome_tagline)
|
||||
json.web_widget_script @inbox.channel.try(:web_widget_script)
|
||||
json.enable_auto_assignment @inbox.enable_auto_assignment
|
||||
json.partial! 'api/v1/models/inbox.json.jbuilder', resource: @inbox
|
||||
|
|
16
app/views/api/v1/models/_inbox.json.jbuilder
Normal file
16
app/views/api/v1/models/_inbox.json.jbuilder
Normal file
|
@ -0,0 +1,16 @@
|
|||
json.id resource.id
|
||||
json.channel_id resource.channel_id
|
||||
json.name resource.name
|
||||
json.channel_type resource.channel_type
|
||||
json.greeting_enabled resource.greeting_enabled
|
||||
json.greeting_message resource.greeting_message
|
||||
json.avatar_url resource.try(:avatar_url)
|
||||
json.page_id resource.channel.try(:page_id)
|
||||
json.widget_color resource.channel.try(:widget_color)
|
||||
json.website_url resource.channel.try(:website_url)
|
||||
json.welcome_title resource.channel.try(:welcome_title)
|
||||
json.welcome_tagline resource.channel.try(:welcome_tagline)
|
||||
json.enable_auto_assignment resource.enable_auto_assignment
|
||||
json.web_widget_script resource.channel.try(:web_widget_script)
|
||||
json.forward_to_address resource.channel.try(:forward_to_address)
|
||||
json.phone_number resource.channel.try(:phone_number)
|
9
db/migrate/20200627115105_create_api_channel.rb
Normal file
9
db/migrate/20200627115105_create_api_channel.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class CreateApiChannel < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :channel_api do |t|
|
||||
t.integer :account_id, null: false
|
||||
t.string :webhook_url, null: false
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
10
db/migrate/20200715124113_create_email_channel.rb
Normal file
10
db/migrate/20200715124113_create_email_channel.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class CreateEmailChannel < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :channel_email do |t|
|
||||
t.integer :account_id, null: false
|
||||
t.string :email, null: false, index: { unique: true }
|
||||
t.string :forward_to_address, null: false, index: { unique: true }
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
17
db/schema.rb
17
db/schema.rb
|
@ -120,6 +120,23 @@ ActiveRecord::Schema.define(version: 2020_07_19_171437) do
|
|||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "channel_api", force: :cascade do |t|
|
||||
t.integer "account_id", null: false
|
||||
t.string "webhook_url", null: false
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
end
|
||||
|
||||
create_table "channel_email", force: :cascade do |t|
|
||||
t.integer "account_id", null: false
|
||||
t.string "email", null: false
|
||||
t.string "forward_to_address", null: false
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["email"], name: "index_channel_email_on_email", unique: true
|
||||
t.index ["forward_to_address"], name: "index_channel_email_on_forward_to_address", unique: true
|
||||
end
|
||||
|
||||
create_table "channel_facebook_pages", id: :serial, force: :cascade do |t|
|
||||
t.string "page_id", null: false
|
||||
t.string "user_access_token", null: false
|
||||
|
|
|
@ -9,10 +9,10 @@ class Integrations::Facebook::MessageCreator
|
|||
|
||||
def perform
|
||||
# begin
|
||||
if outgoing_message_via_echo?
|
||||
create_outgoing_message
|
||||
if agent_message_via_echo?
|
||||
create_agent_message
|
||||
else
|
||||
create_incoming_message
|
||||
create_contact_message
|
||||
end
|
||||
# rescue => e
|
||||
# Raven.capture_exception(e)
|
||||
|
@ -21,22 +21,22 @@ class Integrations::Facebook::MessageCreator
|
|||
|
||||
private
|
||||
|
||||
def outgoing_message_via_echo?
|
||||
def agent_message_via_echo?
|
||||
response.echo? && !response.sent_from_chatwoot_app?
|
||||
# this means that it is an outgoing message from page, but not sent from chatwoot.
|
||||
# User can send from fb page directly on mobile messenger, so this case should be handled as outgoing message
|
||||
# this means that it is an agent message from page, but not sent from chatwoot.
|
||||
# User can send from fb page directly on mobile / web messenger, so this case should be handled as agent message
|
||||
end
|
||||
|
||||
def create_outgoing_message
|
||||
def create_agent_message
|
||||
Channel::FacebookPage.where(page_id: response.sender_id).each do |page|
|
||||
mb = Messages::Outgoing::EchoBuilder.new(response, page.inbox, true)
|
||||
mb = Messages::Facebook::MessageBuilder.new(response, page.inbox, true)
|
||||
mb.perform
|
||||
end
|
||||
end
|
||||
|
||||
def create_incoming_message
|
||||
def create_contact_message
|
||||
Channel::FacebookPage.where(page_id: response.recipient_id).each do |page|
|
||||
mb = Messages::IncomingMessageBuilder.new(response, page.inbox)
|
||||
mb = Messages::Facebook::MessageBuilder.new(response, page.inbox)
|
||||
mb.perform
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Webhooks::Trigger
|
||||
def self.execute(url, payload)
|
||||
RestClient.post(url, payload)
|
||||
RestClient.post(url, payload.to_json, { content_type: :json, accept: :json })
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe ::Messages::IncomingMessageBuilder do
|
||||
describe ::Messages::Facebook::MessageBuilder do
|
||||
subject(:message_builder) { described_class.new(incoming_fb_text_message, facebook_channel.inbox).perform }
|
||||
|
||||
let!(:facebook_channel) { create(:channel_facebook_page) }
|
54
spec/builders/messages/message_builder_spec.rb
Normal file
54
spec/builders/messages/message_builder_spec.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe ::Messages::MessageBuilder do
|
||||
subject(:message_builder) { described_class.new(user, conversation, params).perform }
|
||||
|
||||
let(:account) { create(:account) }
|
||||
let(:user) { create(:user, account: account) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:inbox_member) { create(:inbox_member, inbox: inbox, account: account) }
|
||||
let(:conversation) { create(:conversation, inbox: inbox, account: account) }
|
||||
let(:params) do
|
||||
ActionController::Parameters.new({
|
||||
content: 'test'
|
||||
})
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
it 'creates a message' do
|
||||
message = message_builder
|
||||
expect(message.content).to eq params[:content]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform when message_type is incoming' do
|
||||
context 'when channel is not api' do
|
||||
let(:params) do
|
||||
ActionController::Parameters.new({
|
||||
content: 'test',
|
||||
message_type: 'incoming'
|
||||
})
|
||||
end
|
||||
|
||||
it 'creates throws error when channel is not api' do
|
||||
expect { message_builder }.to raise_error 'Incoming messages are only allowed in Api inboxes'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when channel is api' do
|
||||
let(:channel_api) { create(:channel_api, account: account) }
|
||||
let(:conversation) { create(:conversation, inbox: channel_api.inbox, account: account) }
|
||||
let(:params) do
|
||||
ActionController::Parameters.new({
|
||||
content: 'test',
|
||||
message_type: 'incoming'
|
||||
})
|
||||
end
|
||||
|
||||
it 'creates message when channel is api' do
|
||||
message = message_builder
|
||||
expect(message.message_type).to eq params[:message_type]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -65,6 +65,7 @@ RSpec.describe 'Contacts API', type: :request do
|
|||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
|
||||
it 'creates the contact' do
|
||||
expect do
|
||||
|
@ -74,6 +75,15 @@ RSpec.describe 'Contacts API', type: :request do
|
|||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
|
||||
it 'creates the contact identifier when inbox id is passed' do
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/contacts", headers: admin.create_new_auth_token,
|
||||
params: valid_params.merge({ inbox_id: inbox.id })
|
||||
end.to change(ContactInbox, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
9
spec/factories/channel/channel_api.rb
Normal file
9
spec/factories/channel/channel_api.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
FactoryBot.define do
|
||||
factory :channel_api, class: 'Channel::Api' do
|
||||
webhook_url { 'http://example.com' }
|
||||
account
|
||||
after(:create) do |channel_api|
|
||||
create(:inbox, channel: channel_api, account: channel_api.account)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,10 +5,10 @@ describe Webhooks::Trigger do
|
|||
|
||||
describe '#execute' do
|
||||
it 'triggers webhook' do
|
||||
params = { hello: 'hello' }
|
||||
url = 'htpps://test.com'
|
||||
params = { hello: :hello }
|
||||
url = 'https://test.com'
|
||||
|
||||
expect(RestClient).to receive(:post).with(url, params).once
|
||||
expect(RestClient).to receive(:post).with(url, params.to_json, { accept: :json, content_type: :json }).once
|
||||
trigger.execute(url, params)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue