Change sender_id to contact_id in conversations (#167)

* change sender_id to contact_id in conversations

* Fix failing tests

* Fix seeds

* fix specs

* Fix issues in facebook messenger
This commit is contained in:
Sojan Jose
2019-10-21 00:40:18 +05:30
committed by GitHub
parent 2099dc01a6
commit ba8f055802
20 changed files with 294 additions and 309 deletions

View File

@@ -1,133 +1,128 @@
require 'open-uri'
class Messages::MessageBuilder
# 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, fb_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.
attr_reader :response
# 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, fb_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.
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
module Messages
class MessageBuilder
attr_reader :response
def perform # for incoming
ActiveRecord::Base.transaction do
build_contact
build_conversation
build_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)
end
# build_attachments
rescue StandardError => e
Raven.capture_exception(e)
# change this asap
true
end
private
def build_attachments; end
def contact
@contact ||= @inbox.contacts.find_by(source_id: @sender_id)
end
def build_contact
@contact = @inbox.contacts.create!(contact_params) if contact.nil?
end
def build_message
@message = @conversation.messages.new(message_params)
(response.attachments || []).each do |attachment|
@message.build_attachment(attachment_params(attachment))
end
@message.save!
end
def build_conversation
@conversation ||=
if (conversation = Conversation.find_by(conversation_params))
conversation
else
Conversation.create!(conversation_params)
def perform
ActiveRecord::Base.transaction do
build_contact
build_message
end
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,
sender_id: contact.id
}
end
def message_params
{
account_id: @conversation.account_id,
inbox_id: @conversation.inbox_id,
message_type: @message_type,
content: response.content,
fb_id: response.identifier
}
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 = {}
rescue StandardError => e
Raven.capture_exception(e)
true
end
private
def contact
@contact ||= @inbox.contacts.find_by(source_id: @sender_id)
end
def build_contact
@contact = @inbox.contacts.create!(contact_params) if contact.nil?
end
def build_message
@message = conversation.messages.new(message_params)
(response.attachments || []).each do |attachment|
@message.build_attachment(attachment_params(attachment))
end
@message.save!
end
def build_attachment; end
def conversation
@conversation ||= Conversation.find_by(conversation_params) || Conversation.create!(conversation_params)
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,
fb_id: response.identifier
}
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,
source_id: @sender_id,
remote_avatar_url: result['profile_pic'] || nil
}
end
params = {
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
account_id: @inbox.account_id,
source_id: @sender_id,
remote_avatar_url: result['profile_pic'] || nil
}
end
end

View File

@@ -5,7 +5,7 @@ class Account < ApplicationRecord
has_many :inboxes, dependent: :destroy
has_many :conversations, dependent: :destroy
has_many :contacts, dependent: :destroy
has_many :facebook_pages, dependent: :destroy
has_many :facebook_pages, dependent: :destroy, class_name: '::Channel::FacebookPage'
has_many :telegram_bots, dependent: :destroy
has_many :canned_responses, dependent: :destroy
has_one :subscription, dependent: :destroy

View File

@@ -12,7 +12,7 @@ module Channel
before_destroy :unsubscribe
def name
`Facebook`
'Facebook'
end
private

View File

@@ -6,7 +6,7 @@ class Contact < ApplicationRecord
belongs_to :account
belongs_to :inbox
has_many :conversations, dependent: :destroy, foreign_key: :sender_id
has_many :conversations, dependent: :destroy
mount_uploader :avatar, AvatarUploader
def push_event_data

View File

@@ -13,7 +13,7 @@ class Conversation < ApplicationRecord
belongs_to :account
belongs_to :inbox
belongs_to :assignee, class_name: 'User', optional: true
belongs_to :sender, class_name: 'Contact'
belongs_to :contact
has_many :messages, dependent: :destroy, autosave: true

View File

@@ -23,7 +23,7 @@ module Conversations
end
def push_meta
{ sender: sender.push_event_data, assignee: assignee }
{ sender: contact.push_event_data, assignee: assignee }
end
def push_timestamps

View File

@@ -12,6 +12,8 @@ module Facebook
private
delegate :contact, to: :conversation
def inbox
@inbox ||= message.inbox
end
@@ -20,10 +22,6 @@ module Facebook
@conversation ||= message.conversation
end
def sender
conversation.sender
end
def outgoing_message_from_chatwoot?
# messages sent directly from chatwoot won't have fb_id.
message.outgoing? && !message.fb_id
@@ -37,7 +35,7 @@ module Facebook
def fb_message_params
{
recipient: { id: sender.source_id },
recipient: { id: contact.source_id },
message: { text: message.content }
}
end

View File

@@ -9,16 +9,16 @@ json.data do
json.array! @conversations do |conversation|
json.meta do
json.sender do
json.id conversation.sender_id
json.name conversation.sender.name
json.thumbnail conversation.sender.avatar.thumb.url
json.id conversation.contact.source_id
json.name conversation.contact.name
json.thumbnail conversation.contact.avatar.thumb.url
json.channel conversation.inbox.try(:channel).try(:name)
end
json.assignee conversation.assignee
end
json.id conversation.display_id
if conversation.unread_incoming_messages.count == 0
if conversation.unread_incoming_messages.count.zero?
json.messages [conversation.messages.last.try(:push_event_data)]
else
json.messages conversation.unread_messages.map(&:push_event_data)

View File

@@ -8,8 +8,8 @@ json.data do
json.channel_id inbox.channel_id
json.name inbox.name
json.channel_type inbox.channel_type
json.avatar_url inbox.channel.avatar.url
json.page_id inbox.channel.page_id
json.avatar_url inbox.channel.try(:avatar).try(:url)
json.page_id inbox.channel.try(:page_id)
end
end
end

View File

@@ -0,0 +1,5 @@
class RenameSenderIdToContactInConversation < ActiveRecord::Migration[6.1]
def change
rename_column :conversations, :sender_id, :contact_id
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_10_20_085608) do
ActiveRecord::Schema.define(version: 2019_10_20_173522) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -85,7 +85,7 @@ ActiveRecord::Schema.define(version: 2019_10_20_085608) do
t.integer "assignee_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "sender_id"
t.bigint "contact_id"
t.integer "display_id", null: false
t.datetime "user_last_seen_at"
t.datetime "agent_last_seen_at"

View File

@@ -10,5 +10,5 @@ inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support
InboxMember.create!(user: user, inbox: inbox)
contact = Contact.create!(name: 'jane', email: 'jane@example.com', phone_number: '0000', inbox: inbox, account: account)
Conversation.create!(account: account, inbox: inbox, status: :open, assignee_id: 1, sender: contact)
Message.create!(content: 'Hello', account_id: 1, inbox_id: 1, conversation_id: 1, message_type: :incoming)
conversation = Conversation.create!(account: account, inbox: inbox, status: :open, assignee: user, contact: contact)
Message.create!(content: 'Hello', account: account, inbox: inbox, conversation: conversation, message_type: :incoming)

View File

@@ -1,30 +1,34 @@
# frozen_string_literal: true
class Integrations::Facebook::DeliveryStatus
def initialize(params)
@params = params
end
module Integrations
module Facebook
class DeliveryStatus
def initialize(params)
@params = params
end
def perform
update_message_status
end
def perform
update_message_status
end
private
private
def sender_id
@params.sender['id']
end
def sender_id
@params.sender['id']
end
def contact_id
@contact ||= ::Contact.find_by(source_id: sender_id)
end
def contact
Contact.find_by(source_id: sender_id)
end
def conversation
@conversation ||= ::Conversation.find_by(sender_id: contact_id)
end
def conversation
@conversation ||= ::Conversation.find_by(contact_id: contact.id)
end
def update_message_status
conversation.user_last_seen_at = @params.at
conversation.save!
def update_message_status
conversation.user_last_seen_at = @params.at
conversation.save!
end
end
end
end

View File

@@ -1,43 +1,47 @@
# frozen_string_literal: true
class Integrations::Facebook::MessageCreator
attr_reader :response
module Integrations
module Facebook
class MessageCreator
attr_reader :response
def initialize(response)
@response = response
end
def initialize(response)
@response = response
end
def perform
# begin
if outgoing_message_via_echo?
create_outgoing_message
else
create_incoming_message
end
# rescue => e
# Raven.capture_exception(e)
# end
end
def perform
# begin
if outgoing_message_via_echo?
create_outgoing_message
else
create_incoming_message
end
# rescue => e
# Raven.capture_exception(e)
# end
end
private
private
def outgoing_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
end
def outgoing_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
end
def create_outgoing_message
Channel::FacebookPage.where(page_id: response.sender_id).each do |page|
mb = Messages::Outgoing::EchoBuilder.new(response, page.inbox, true)
mb.perform
end
end
def create_outgoing_message
Channel::FacebookPage.where(page_id: response.sender_id).each do |page|
mb = Messages::Outgoing::EchoBuilder.new(response, page.inbox, true)
mb.perform
end
end
def create_incoming_message
Channel::FacebookPage.where(page_id: response.recipient_id).each do |page|
mb = Messages::IncomingMessageBuilder.new(response, page.inbox)
mb.perform
def create_incoming_message
Channel::FacebookPage.where(page_id: response.recipient_id).each do |page|
mb = Messages::IncomingMessageBuilder.new(response, page.inbox)
mb.perform
end
end
end
end
end

View File

@@ -1,48 +1,52 @@
# frozen_string_literal: true
class Integrations::Facebook::MessageParser
def initialize(response_json)
@response = response_json
end
module Integrations
module Facebook
class MessageParser
def initialize(response_json)
@response = response_json
end
def sender_id
@response.sender['id']
end
def sender_id
@response.sender['id']
end
def recipient_id
@response.recipient['id']
end
def recipient_id
@response.recipient['id']
end
def time_stamp
@response.sent_at
end
def time_stamp
@response.sent_at
end
def content
@response.text
end
def content
@response.text
end
def sequence
@response.seq
end
def sequence
@response.seq
end
def attachments
@response.attachments
end
def attachments
@response.attachments
end
def identifier
@response.id
end
def identifier
@response.id
end
def echo?
@response.echo?
end
def echo?
@response.echo?
end
def app_id
@response.app_id
end
def app_id
@response.app_id
end
def sent_from_chatwoot_app?
app_id && app_id == ENV['fb_app_id'].to_i
def sent_from_chatwoot_app?
app_id && app_id == ENV['fb_app_id'].to_i
end
end
end
end

View File

@@ -1,63 +1,61 @@
# frozen_string_literal: true
class Integrations::Widget::IncomingMessageBuilder
# params = {
# contact_id: 1,
# inbox_id: 1,
# content: "Hello world"
# }
module Integrations
module Widget
class Integrations::Widget::IncomingMessageBuilder
# params = {
# contact_id: 1,
# inbox_id: 1,
# content: "Hello world"
# }
attr_accessor :options, :message
attr_accessor :options, :message
def initialize(options)
@options = options
end
def initialize(options)
@options = options
end
def perform
ActiveRecord::Base.transaction do
build_conversation
build_message
def perform
ActiveRecord::Base.transaction do
build_message
end
end
private
def inbox
@inbox ||= Inbox.find(options[:inbox_id])
end
def contact
@contact ||= Contact.find(options[:contact_id])
end
def conversation
@conversation ||= Conversation.find_by(conversation_params) || Conversation.create!(conversation_params)
end
def build_message
@message = conversation.messages.new(message_params)
@message.save!
end
def conversation_params
{
account_id: inbox.account_id,
inbox_id: inbox.id,
contact_id: options[:contact_id]
}
end
def message_params
{
account_id: conversation.account_id,
inbox_id: conversation.inbox_id,
message_type: 0,
content: options[:content]
}
end
end
end
private
def inbox
@inbox ||= Inbox.find(options[:inbox_id])
end
def contact
@contact ||= Contact.find(options[:contact_id])
end
def build_conversation
@conversation ||=
if (conversation = Conversation.find_by(conversation_params))
conversation
else
Conversation.create!(conversation_params)
end
end
def build_message
@message = @conversation.messages.new(message_params)
@message.save!
end
def conversation_params
{
account_id: inbox.account_id,
inbox_id: inbox.id,
sender_id: options[:contact_id]
}
end
def message_params
{
account_id: @conversation.account_id,
inbox_id: @conversation.inbox_id,
message_type: 0,
content: options[:content]
}
end
end

View File

@@ -16,7 +16,7 @@ FactoryBot.define do
account: conversation.account,
channel: create(:channel_widget, account: conversation.account)
)
conversation.sender ||= create(:contact, account: conversation.account)
conversation.contact ||= create(:contact, account: conversation.account)
conversation.assignee ||= create(:user)
end
end

View File

@@ -1,23 +0,0 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
content: MyText
account_id: 1
channel_id: 1
inbox_id: 1
conversation_id: 1
sender_id: 1
sender_type: MyString
reciever_id: 1
reciever_type: MyString
two:
content: MyText
account_id: 1
channel_id: 1
inbox_id: 1
conversation_id: 1
sender_id: 1
sender_type: MyString
reciever_id: 1
reciever_type: MyString

View File

@@ -79,7 +79,7 @@ RSpec.describe Conversation, type: :model do
create(
:conversation,
account: account,
sender: create(:contact, account: account),
contact: create(:contact, account: account),
inbox: inbox,
assignee: nil
)
@@ -205,7 +205,7 @@ RSpec.describe Conversation, type: :model do
let(:expected_data) do
{
meta: {
sender: conversation.sender.push_event_data,
sender: conversation.contact.push_event_data,
assignee: conversation.assignee
},
id: conversation.display_id,

View File

@@ -14,7 +14,7 @@ RSpec.describe Conversations::EventDataPresenter do
let(:expected_data) do
{
meta: {
sender: conversation.sender.push_event_data,
sender: conversation.contact.push_event_data,
assignee: conversation.assignee
},
id: conversation.display_id,