diff --git a/app/builders/messages/facebook/message_builder.rb b/app/builders/messages/facebook/message_builder.rb
new file mode 100644
index 000000000..0095b9e1b
--- /dev/null
+++ b/app/builders/messages/facebook/message_builder.rb
@@ -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
diff --git a/app/builders/messages/incoming_message_builder.rb b/app/builders/messages/incoming_message_builder.rb
deleted file mode 100644
index 3a01c703a..000000000
--- a/app/builders/messages/incoming_message_builder.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class Messages::IncomingMessageBuilder < Messages::MessageBuilder
-end
diff --git a/app/builders/messages/message_builder.rb b/app/builders/messages/message_builder.rb
index a8251edb4..e53bcf377 100644
--- a/app/builders/messages/message_builder.rb
+++ b/app/builders/messages/message_builder.rb
@@ -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
diff --git a/app/builders/messages/outgoing/echo_builder.rb b/app/builders/messages/outgoing/echo_builder.rb
deleted file mode 100644
index 7d5c3fa79..000000000
--- a/app/builders/messages/outgoing/echo_builder.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class Messages::Outgoing::EchoBuilder < ::Messages::MessageBuilder
-end
diff --git a/app/builders/messages/outgoing/normal_builder.rb b/app/builders/messages/outgoing/normal_builder.rb
deleted file mode 100644
index 78572b0ee..000000000
--- a/app/builders/messages/outgoing/normal_builder.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb
index 2208a2cf6..7f7e7afdb 100644
--- a/app/controllers/api/v1/accounts/contacts_controller.rb
+++ b/app/controllers/api/v1/accounts/contacts_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/accounts/conversations/messages_controller.rb b/app/controllers/api/v1/accounts/conversations/messages_controller.rb
index c4d595771..54cf7c595 100644
--- a/app/controllers/api/v1/accounts/conversations/messages_controller.rb
+++ b/app/controllers/api/v1/accounts/conversations/messages_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb
index 45a190bfb..bb2024d50 100644
--- a/app/controllers/api/v1/accounts/inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/inboxes_controller.rb
@@ -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
diff --git a/app/javascript/dashboard/assets/images/channels/api.png b/app/javascript/dashboard/assets/images/channels/api.png
new file mode 100644
index 000000000..d9919fc21
Binary files /dev/null and b/app/javascript/dashboard/assets/images/channels/api.png differ
diff --git a/app/javascript/dashboard/assets/images/channels/email.png b/app/javascript/dashboard/assets/images/channels/email.png
new file mode 100644
index 000000000..304400e09
Binary files /dev/null and b/app/javascript/dashboard/assets/images/channels/email.png differ
diff --git a/app/javascript/dashboard/components/layout/SidebarItem.vue b/app/javascript/dashboard/components/layout/SidebarItem.vue
index 7ef2dfa88..7a305fbd3 100644
--- a/app/javascript/dashboard/components/layout/SidebarItem.vue
+++ b/app/javascript/dashboard/components/layout/SidebarItem.vue
@@ -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 '';
}
diff --git a/app/javascript/dashboard/components/widgets/ChannelItem.vue b/app/javascript/dashboard/components/widgets/ChannelItem.vue
index 52f4aff52..21c0aaf91 100644
--- a/app/javascript/dashboard/components/widgets/ChannelItem.vue
+++ b/app/javascript/dashboard/components/widgets/ChannelItem.vue
@@ -16,6 +16,14 @@
v-if="channel === 'telegram'"
src="~dashboard/assets/images/channels/telegram.png"
/>
+
+
+