Chore: Routine Bugfixes and enhancements (#979)

- Fix slack scopes
- Docs for authentication
Fixes: #704 , #973
This commit is contained in:
Sojan Jose 2020-06-25 23:35:16 +05:30 committed by GitHub
parent 0aab717bb3
commit 4f83d5451e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 254 additions and 147 deletions

View file

@ -98,7 +98,7 @@ jobs:
- run: - run:
name: Run backend tests name: Run backend tests
command: | command: |
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) --profile=10
~/tmp/cc-test-reporter format-coverage -t simplecov -o ~/tmp/codeclimate.backend.json coverage/backend/.resultset.json ~/tmp/cc-test-reporter format-coverage -t simplecov -o ~/tmp/codeclimate.backend.json coverage/backend/.resultset.json
- persist_to_workspace: - persist_to_workspace:
root: ~/tmp root: ~/tmp

View file

@ -93,10 +93,10 @@ GEM
rake (>= 10.4, < 14.0) rake (>= 10.4, < 14.0)
ast (2.4.1) ast (2.4.1)
attr_extras (6.2.4) attr_extras (6.2.4)
autoprefixer-rails (9.7.6) autoprefixer-rails (9.8.2)
execjs execjs
aws-eventstream (1.1.0) aws-eventstream (1.1.0)
aws-partitions (1.329.0) aws-partitions (1.332.0)
aws-sdk-core (3.100.0) aws-sdk-core (3.100.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0) aws-partitions (~> 1, >= 1.239.0)
@ -105,12 +105,12 @@ GEM
aws-sdk-kms (1.34.1) aws-sdk-kms (1.34.1)
aws-sdk-core (~> 3, >= 3.99.0) aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.68.1) aws-sdk-s3 (1.69.1)
aws-sdk-core (~> 3, >= 3.99.0) aws-sdk-core (~> 3, >= 3.99.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.4) aws-sigv4 (1.2.0)
aws-eventstream (~> 1.0, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
axiom-types (0.1.1) axiom-types (0.1.1)
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
@ -163,7 +163,7 @@ GEM
devise (> 3.5.2, < 5) devise (> 3.5.2, < 5)
rails (>= 4.2.0, < 6.1) rails (>= 4.2.0, < 6.1)
sprockets (= 3.7.2) sprockets (= 3.7.2)
diff-lcs (1.3) diff-lcs (1.4)
digest-crc (0.5.1) digest-crc (0.5.1)
docile (1.3.2) docile (1.3.2)
domain_name (0.5.20190701) domain_name (0.5.20190701)
@ -178,11 +178,11 @@ GEM
facebook-messenger (1.5.0) facebook-messenger (1.5.0)
httparty (~> 0.13, >= 0.13.7) httparty (~> 0.13, >= 0.13.7)
rack (>= 1.4.5) rack (>= 1.4.5)
factory_bot (5.2.0) factory_bot (6.0.2)
activesupport (>= 4.2.0) activesupport (>= 5.0.0)
factory_bot_rails (5.2.0) factory_bot_rails (6.0.0)
factory_bot (~> 5.2.0) factory_bot (~> 6.0.0)
railties (>= 4.2.0) railties (>= 5.0.0)
faker (2.12.0) faker (2.12.0)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
faraday (1.0.1) faraday (1.0.1)
@ -197,7 +197,7 @@ GEM
gli (2.19.1) gli (2.19.1)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
google-api-client (0.40.2) google-api-client (0.41.0)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9) googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0) httpclient (>= 2.8.1, < 3.0)
@ -218,7 +218,7 @@ GEM
google-cloud-core (~> 1.2) google-cloud-core (~> 1.2)
googleauth (~> 0.9) googleauth (~> 0.9)
mini_mime (~> 1.0) mini_mime (~> 1.0)
googleauth (0.12.0) googleauth (0.13.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0) jwt (>= 1.4, < 3.0)
memoist (~> 0.16) memoist (~> 0.16)
@ -306,8 +306,8 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (1.1.0) os (1.1.0)
parallel (1.19.2) parallel (1.19.2)
parser (2.7.1.3) parser (2.7.1.4)
ast (~> 2.4.0) ast (~> 2.4.1)
pg (1.2.3) pg (1.2.3)
pry (0.13.1) pry (0.13.1)
coderay (~> 1.1) coderay (~> 1.1)
@ -367,7 +367,7 @@ GEM
redis-rack-cache (2.2.1) redis-rack-cache (2.2.1)
rack-cache (>= 1.10, < 2) rack-cache (>= 1.10, < 2)
redis-store (>= 1.6, < 2) redis-store (>= 1.6, < 2)
redis-store (1.8.2) redis-store (1.9.0)
redis (>= 4, < 5) redis (>= 4, < 5)
regexp_parser (1.7.1) regexp_parser (1.7.1)
representable (3.0.4) representable (3.0.4)
@ -401,13 +401,13 @@ GEM
rspec-mocks (~> 3.9) rspec-mocks (~> 3.9)
rspec-support (~> 3.9) rspec-support (~> 3.9)
rspec-support (3.9.3) rspec-support (3.9.3)
rubocop (0.85.1) rubocop (0.86.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.7.0.1) parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7) regexp_parser (>= 1.7)
rexml rexml
rubocop-ast (>= 0.0.3) rubocop-ast (>= 0.0.3, < 1.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0) unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.0.3) rubocop-ast (0.0.3)

View file

@ -14,7 +14,7 @@ class Api::V1::Accounts::LabelsController < Api::V1::Accounts::BaseController
end end
def update def update
@label.update(permitted_params) @label.update!(permitted_params)
end end
def destroy def destroy

View file

@ -59,11 +59,19 @@ class ApplicationController < ActionController::Base
render json: exception.to_hash, status: exception.http_status render json: exception.to_hash, status: exception.http_status
end end
def locale_from_params
I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil
end
def locale_from_account(account)
I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil
end
def switch_locale(account) def switch_locale(account)
# priority is for locale set in query string (mostly for widget/from js sdk) # priority is for locale set in query string (mostly for widget/from js sdk)
locale ||= (I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil) locale ||= locale_from_params
# if local is not set in param, lets try account # if local is not set in param, lets try account
locale ||= (I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil) locale ||= locale_from_account(account)
I18n.locale = locale || I18n.default_locale I18n.locale = locale || I18n.default_locale
end end

View file

@ -4,6 +4,7 @@ class WidgetsController < ActionController::Base
before_action :set_token before_action :set_token
before_action :set_contact before_action :set_contact
before_action :build_contact before_action :build_contact
after_action :allow_iframe_requests
def index; end def index; end
@ -50,4 +51,8 @@ class WidgetsController < ActionController::Base
def permitted_params def permitted_params
params.permit(:website_token, :cw_conversation) params.permit(:website_token, :cw_conversation)
end end
def allow_iframe_requests
response.headers.delete('X-Frame-Options')
end
end end

View file

@ -62,7 +62,7 @@ class ConversationFinder
def find_all_conversations def find_all_conversations
@conversations = current_account.conversations.includes( @conversations = current_account.conversations.includes(
:assignee, :contact, :inbox :assignee, :inbox, contact: [:avatar_attachment]
).where(inbox_id: @inbox_ids) ).where(inbox_id: @inbox_ids)
end end

View file

@ -1,6 +1,6 @@
import { required, minLength } from 'vuelidate/lib/validators'; import { required, minLength } from 'vuelidate/lib/validators';
export const validLabelCharacters = (str = '') => /^[\w-_]+$/g.test(str); export const validLabelCharacters = (str = '') => !!str && !str.includes(' ');
export default { export default {
title: { title: {

View file

@ -4,7 +4,7 @@ class HookJob < ApplicationJob
def perform(hook, message) def perform(hook, message)
return unless hook.slack? return unless hook.slack?
Integrations::Slack::OutgoingMessageBuilder.perform(hook, message) Integrations::Slack::SendOnSlackService.new(message: message, hook: hook).perform
rescue StandardError => e rescue StandardError => e
Raven.capture_exception(e) Raven.capture_exception(e)
end end

View file

@ -138,11 +138,11 @@ class Message < ApplicationRecord
def send_reply def send_reply
channel_name = conversation.inbox.channel.class.to_s channel_name = conversation.inbox.channel.class.to_s
if channel_name == 'Channel::FacebookPage' if channel_name == 'Channel::FacebookPage'
::Facebook::SendReplyService.new(message: self).perform ::Facebook::SendOnFacebookService.new(message: self).perform
elsif channel_name == 'Channel::TwitterProfile' elsif channel_name == 'Channel::TwitterProfile'
::Twitter::SendReplyService.new(message: self).perform ::Twitter::SendOnTwitterService.new(message: self).perform
elsif channel_name == 'Channel::TwilioSms' elsif channel_name == 'Channel::TwilioSms'
::Twilio::OutgoingMessageService.new(message: self).perform ::Twilio::SendOnTwilioService.new(message: self).perform
end end
end end

View file

@ -0,0 +1,55 @@
#######################################
# To create an external channel reply service
# - Inherit this as the base class.
# - Implement `channel_class` method in your child class.
# - Implement `perform_reply` method in your child class.
# - Implement additional custom logic for your `perform_reply` method.
# - When required override the validation_methods.
# - Use Childclass.new.perform.
######################################
class Base::SendOnChannelService
pattr_initialize [:message!]
def perform
validate_target_channel
return unless outgoing_message?
return if invalid_message?
perform_reply
end
private
delegate :conversation, to: :message
delegate :contact, :contact_inbox, :inbox, to: :conversation
delegate :channel, to: :inbox
def channel_class
raise 'Overwrite this method in child class'
end
def perform_reply
raise 'Overwrite this method in child class'
end
def outgoing_message_originated_from_channel?
# TODO: we need to refactor this logic as more integrations comes by
# chatwoot messages won't have source id at the moment
# outgoing messages may be created in slack which should be send to the channel
message.source_id.present? && !message.source_id.starts_with?('slack_')
end
def outgoing_message?
message.outgoing? || message.template?
end
def invalid_message?
# private notes aren't send to the channels
# we should also avoid the case of message loops, when outgoing messages are created from channel
message.private? || outgoing_message_originated_from_channel?
end
def validate_target_channel
raise 'Invalid channel service was called' if inbox.channel.class != channel_class
end
end

View file

@ -1,37 +1,14 @@
class Facebook::SendReplyService class Facebook::SendOnFacebookService < Base::SendOnChannelService
pattr_initialize [:message!]
def perform
return if message.private
return if inbox.channel.class.to_s != 'Channel::FacebookPage'
return unless outgoing_message_from_chatwoot?
FacebookBot::Bot.deliver(delivery_params, access_token: message.channel_token)
end
private private
delegate :contact, to: :conversation def channel_class
Channel::FacebookPage
def inbox
@inbox ||= message.inbox
end end
def conversation def perform_reply
@conversation ||= message.conversation FacebookBot::Bot.deliver(delivery_params, access_token: message.channel_token)
end end
def outgoing_message_from_chatwoot?
# messages sent directly from chatwoot won't have source_id.
(message.outgoing? || message.template?) && !message.source_id
end
# def reopen_lock
# if message.incoming? && conversation.locked?
# conversation.unlock!
# end
# end
def fb_text_message_params def fb_text_message_params
{ {
recipient: { id: contact.get_source_id(inbox.id) }, recipient: { id: contact.get_source_id(inbox.id) },

View file

@ -1,22 +1,15 @@
class Twilio::OutgoingMessageService class Twilio::SendOnTwilioService < Base::SendOnChannelService
pattr_initialize [:message!] private
def perform def channel_class
return if message.private Channel::TwilioSms
return if message.source_id end
return if inbox.channel.class.to_s != 'Channel::TwilioSms'
return unless outgoing_message?
def perform_reply
twilio_message = client.messages.create(message_params) twilio_message = client.messages.create(message_params)
message.update!(source_id: twilio_message.sid) message.update!(source_id: twilio_message.sid)
end end
private
delegate :conversation, to: :message
delegate :contact, to: :conversation
delegate :contact_inbox, to: :conversation
def message_params def message_params
params = { params = {
body: message.content, body: message.content,

View file

@ -1,16 +1,17 @@
class Twitter::SendReplyService class Twitter::SendOnTwitterService < Base::SendOnChannelService
pattr_initialize [:message!] pattr_initialize [:message!]
def perform private
return if message.private
return if message.source_id
return if inbox.channel.class.to_s != 'Channel::TwitterProfile'
return unless outgoing_message_from_chatwoot?
send_reply delegate :additional_attributes, to: :contact
def channel_class
Channel::TwitterProfile
end end
private def perform_reply
conversation_type == 'tweet' ? send_tweet_reply : send_direct_message
end
def twitter_client def twitter_client
Twitty::Facade.new do |config| Twitty::Facade.new do |config|
@ -50,19 +51,4 @@ class Twitter::SendReplyService
Rails.logger.info 'TWITTER_TWEET_REPLY_ERROR' + response.body Rails.logger.info 'TWITTER_TWEET_REPLY_ERROR' + response.body
end end
end end
def send_reply
conversation_type == 'tweet' ? send_tweet_reply : send_direct_message
end
def outgoing_message_from_chatwoot?
(message.outgoing? || message.template?)
end
delegate :additional_attributes, to: :contact
delegate :contact, to: :conversation
delegate :contact_inbox, to: :conversation
delegate :conversation, to: :message
delegate :inbox, to: :conversation
delegate :channel, to: :inbox
end end

View file

@ -12,7 +12,7 @@ json.id conversation.display_id
if conversation.unread_incoming_messages.count.zero? if conversation.unread_incoming_messages.count.zero?
json.messages [conversation.messages.last.try(:push_event_data)] json.messages [conversation.messages.last.try(:push_event_data)]
else else
json.messages conversation.unread_messages.map(&:push_event_data) json.messages conversation.unread_messages.includes([:user, :attachments]).map(&:push_event_data)
end end
json.inbox_id conversation.inbox_id json.inbox_id conversation.inbox_id

View file

@ -27,10 +27,6 @@ module Chatwoot
config.generators.javascripts = false config.generators.javascripts = false
config.generators.stylesheets = false config.generators.stylesheets = false
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'ALLOWALL'
}
# Custom chatwoot configurations # Custom chatwoot configurations
config.x = config_for(:app).with_indifferent_access config.x = config_for(:app).with_indifferent_access
end end

View file

@ -7,3 +7,7 @@ redis = Rails.env.test? ? MockRedis.new : Redis.new(app_redis_config)
# Alfred - Used currently for round robin and conversation emails. # Alfred - Used currently for round robin and conversation emails.
# Add here as you use it for more features # Add here as you use it for more features
$alfred = Redis::Namespace.new('alfred', redis: redis, warning: true) $alfred = Redis::Namespace.new('alfred', redis: redis, warning: true)
# https://github.com/mperham/sidekiq/issues/4591
# TODO once sidekiq remove we can remove this
Redis.exists_returns_integer = false

View file

@ -3,7 +3,7 @@ slack:
name: Slack name: Slack
logo: slack.png logo: slack.png
description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack."
action: https://slack.com/oauth/v2/authorize?scope=commands,chat:write,channels:manage,channels:join,groups:write,im:write,mpim:write,users:read,users:read.email,chat:write.customize action: https://slack.com/oauth/v2/authorize?scope=commands,chat:write,channels:read,channels:manage,channels:join,groups:write,im:write,mpim:write,users:read,users:read.email,chat:write.customize,channels:history,groups:history,mpim:history,im:history
webhooks: webhooks:
id: webhook id: webhook
name: Webhooks name: Webhooks

View file

@ -16,7 +16,7 @@ Once you register your Slack App, you will have to obtain the `Client Id` and `C
3) Head over to the `OAuth & permissions` section under `features` tab. 3) Head over to the `OAuth & permissions` section under `features` tab.
4) In the redirect URLs, Add your Chatwoot installation base url. 4) In the redirect URLs, Add your Chatwoot installation base url.
5) In the scopes section configure the given scopes for bot token scopes. 5) In the scopes section configure the given scopes for bot token scopes.
`commands,chat:write,channels:manage,channels:join,groups:write,im:write,mpim:write,users:read,users:read.email,chat:write.customize` `commands,chat:write,channels:read,channels:manage,channels:join,groups:write,im:write,mpim:write,users:read,users:read.email,chat:write.customize,channels:history,groups:history,mpim:history,im:history`
6) Head over to the `events subscriptions` section under `features` tab. 6) Head over to the `events subscriptions` section under `features` tab.
7) Enable events and configure the the given request url `{chatwoot installation url}/api/v1/integrations/webhooks` 7) Enable events and configure the the given request url `{chatwoot installation url}/api/v1/integrations/webhooks`
8) Subscribe to the following bot events `message.channels` , `message.groups`, `message.im`, `message.mpim` 8) Subscribe to the following bot events `message.channels` , `message.groups`, `message.im`, `message.mpim`

View file

@ -17,10 +17,7 @@ class Integrations::Slack::ChannelBuilder
end end
def slack_client def slack_client
Slack.configure do |config| @slack_client ||= Slack::Web::Client.new(token: hook.access_token)
config.token = hook.access_token
end
Slack::Web::Client.new
end end
def find_or_create_channel def find_or_create_channel
@ -29,6 +26,7 @@ class Integrations::Slack::ChannelBuilder
end end
def update_reference_id def update_reference_id
@hook.update(reference_id: channel['id']) slack_client.conversations_join(channel: channel[:id])
@hook.update(reference_id: channel[:id])
end end
end end

View file

@ -89,9 +89,6 @@ class Integrations::Slack::IncomingMessageBuilder
end end
def slack_client def slack_client
Slack.configure do |config| @slack_client ||= Slack::Web::Client.new(token: @integration_hook.access_token)
config.token = integration_hook.access_token
end
Slack::Web::Client.new
end end
end end

View file

@ -1,30 +1,23 @@
class Integrations::Slack::OutgoingMessageBuilder class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService
attr_reader :hook, :message pattr_initialize [:message!, :hook!]
def self.perform(hook, message)
new(hook, message).perform
end
def initialize(hook, message)
@hook = hook
@message = message
end
def perform def perform
return if message.source_id.present? # overriding the base class logic since the validations are different in this case.
# FIXME: for now we will only send messages from widget to slack
return unless channel.is_a?(Channel::WebWidget)
# we don't want message loop in slack
return if message.source_id.try(:starts_with?, 'slack_')
# we don't want to start slack thread from agent conversation as of now
return if message.outgoing? && conversation.identifier.blank?
send_message perform_reply
update_reference_id
end end
private private
def conversation def perform_reply
@conversation ||= message.conversation send_message
end update_reference_id
def contact
@contact ||= conversation.contact
end end
def agent def agent
@ -32,8 +25,9 @@ class Integrations::Slack::OutgoingMessageBuilder
end end
def message_content def message_content
private_indicator = message.private? ? 'private: ' : ''
if conversation.identifier.present? if conversation.identifier.present?
message.content "#{private_indicator}#{message.content}"
else else
"*Inbox: #{message.inbox.name}* \n\n #{message.content}" "*Inbox: #{message.inbox.name}* \n\n #{message.content}"
end end
@ -59,14 +53,10 @@ class Integrations::Slack::OutgoingMessageBuilder
def update_reference_id def update_reference_id
return if conversation.identifier return if conversation.identifier
conversation.identifier = @slack_message['ts'] conversation.update!(identifier: @slack_message['ts'])
conversation.save!
end end
def slack_client def slack_client
Slack.configure do |config| @slack_client ||= Slack::Web::Client.new(token: hook.access_token)
config.token = hook.access_token
end
Slack::Web::Client.new
end end
end end

View file

@ -14,15 +14,16 @@ RSpec.describe 'Integration Apps API', type: :request do
context 'when it is an authenticated user' do context 'when it is an authenticated user' do
let(:agent) { create(:user, account: account, role: :agent) } let(:agent) { create(:user, account: account, role: :agent) }
it 'returns all the apps' do it 'returns all active apps' do
first_app = Integrations::App.all.find(&:active?)
get api_v1_account_integrations_apps_url(account), get api_v1_account_integrations_apps_url(account),
headers: agent.create_new_auth_token, headers: agent.create_new_auth_token,
as: :json as: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
app = JSON.parse(response.body)['payload'].first apps = JSON.parse(response.body)['payload'].first
expect(app['id']).to eql('webhook') expect(apps['id']).to eql(first_app.id)
expect(app['name']).to eql('Webhooks') expect(apps['name']).to eql(first_app.name)
end end
end end
end end

View file

@ -1,6 +1,6 @@
require 'rails_helper' require 'rails_helper'
describe Integrations::Slack::OutgoingMessageBuilder do describe Integrations::Slack::SendOnSlackService do
let(:account) { create(:account) } let(:account) { create(:account) }
let!(:inbox) { create(:inbox, account: account) } let!(:inbox) { create(:inbox, account: account) }
let!(:contact) { create(:contact) } let!(:contact) { create(:contact) }
@ -11,7 +11,7 @@ describe Integrations::Slack::OutgoingMessageBuilder do
describe '#perform' do describe '#perform' do
it 'sent message to slack' do it 'sent message to slack' do
builder = described_class.new(hook, message) builder = described_class.new(message: message, hook: hook)
stub_request(:post, 'https://slack.com/api/chat.postMessage') stub_request(:post, 'https://slack.com/api/chat.postMessage')
.to_return(status: 200, body: '', headers: {}) .to_return(status: 200, body: '', headers: {})
slack_client = double slack_client = double

View file

@ -1,6 +1,6 @@
require 'rails_helper' require 'rails_helper'
describe Facebook::SendReplyService do describe Facebook::SendOnFacebookService do
subject(:send_reply_service) { described_class.new(message: message) } subject(:send_reply_service) { described_class.new(message: message) }
before do before do

View file

@ -1,6 +1,6 @@
require 'rails_helper' require 'rails_helper'
describe Twilio::OutgoingMessageService do describe Twilio::SendOnTwilioService do
subject(:outgoing_message_service) { described_class.new(message: message) } subject(:outgoing_message_service) { described_class.new(message: message) }
let(:twilio_client) { instance_double(::Twilio::REST::Client) } let(:twilio_client) { instance_double(::Twilio::REST::Client) }

View file

@ -1,6 +1,6 @@
require 'rails_helper' require 'rails_helper'
describe Twitter::SendReplyService do describe Twitter::SendOnTwitterService do
subject(:send_reply_service) { described_class.new(message: message) } subject(:send_reply_service) { described_class.new(message: message) }
let(:twitter_client) { instance_double(::Twitty::Facade) } let(:twitter_client) { instance_double(::Twitty::Facade) }

View file

@ -14,6 +14,4 @@ RSpec.configure do |config|
end end
config.shared_context_metadata_behavior = :apply_to_host_groups config.shared_context_metadata_behavior = :apply_to_host_groups
# config.include Rails.application.routes.url_helpers
end end

View file

@ -17,6 +17,24 @@ produces:
- application/json; charset=utf-8 - application/json; charset=utf-8
consumes: consumes:
- application/json; charset=utf-8 - application/json; charset=utf-8
securityDefinitions:
userApiKey:
type: apiKey
in: header
name: api_access_token
description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user.
agentBotApiKey:
type: apiKey
in: header
name: api_access_token
description: This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis.
superAdminApiKey:
type: apiKey
in: header
name: api_access_token
description: This token is only for the system admin or obtained via rails console. This token is to be used rarely for cases like creating a pre verified user through api from external system.
security:
- userApiKey: []
paths: paths:
$ref: ./paths/index.yml $ref: ./paths/index.yml

View file

@ -45,6 +45,9 @@ post:
operationId: newConversation operationId: newConversation
summary: Create New Conversation summary: Create New Conversation
description: Create conversation description: Create conversation
security:
- userApiKey: []
- agentBotApiKey: []
parameters: parameters:
- name: data - name: data
in: body in: body

View file

@ -30,6 +30,9 @@ post:
operationId: conversationNewMessage operationId: conversationNewMessage
summary: Create New Message summary: Create New Message
description: All the agent replies are created as new messages through this endpoint description: All the agent replies are created as new messages through this endpoint
security:
- userApiKey: []
- agentBotApiKey: []
parameters: parameters:
- name: id - name: id
in: path in: path

View file

@ -4,6 +4,9 @@ post:
operationId: conversationToggleStatus operationId: conversationToggleStatus
summary: Toggle Status summary: Toggle Status
description: Toggles the status of the conversation between open and resolved description: Toggles the status of the conversation between open and resolved
security:
- userApiKey: []
- agentBotApiKey: []
parameters: parameters:
- name: id - name: id
in: path in: path

View file

@ -24,6 +24,33 @@
"consumes": [ "consumes": [
"application/json; charset=utf-8" "application/json; charset=utf-8"
], ],
"securityDefinitions": {
"userApiKey": {
"type": "apiKey",
"in": "header",
"name": "api_access_token",
"description": "This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user."
},
"agentBotApiKey": {
"type": "apiKey",
"in": "header",
"name": "api_access_token",
"description": "This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis."
},
"superAdminApiKey": {
"type": "apiKey",
"in": "header",
"name": "api_access_token",
"description": "This token is only for the system admin or obtained via rails console. This token is to be used rarely for cases like creating a pre verified user through api from external system."
}
},
"security": [
{
"userApiKey": [
]
}
],
"paths": { "paths": {
"/accounts/{account_id}/inboxes": { "/accounts/{account_id}/inboxes": {
"post": { "post": {
@ -325,6 +352,18 @@
"operationId": "newConversation", "operationId": "newConversation",
"summary": "Create New Conversation", "summary": "Create New Conversation",
"description": "Create conversation", "description": "Create conversation",
"security": [
{
"userApiKey": [
]
},
{
"agentBotApiKey": [
]
}
],
"parameters": [ "parameters": [
{ {
"name": "data", "name": "data",
@ -409,6 +448,18 @@
"operationId": "conversationToggleStatus", "operationId": "conversationToggleStatus",
"summary": "Toggle Status", "summary": "Toggle Status",
"description": "Toggles the status of the conversation between open and resolved", "description": "Toggles the status of the conversation between open and resolved",
"security": [
{
"userApiKey": [
]
},
{
"agentBotApiKey": [
]
}
],
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
@ -428,7 +479,8 @@
"type": "string", "type": "string",
"enum": [ "enum": [
"open", "open",
"resolved" "resolved",
"bot"
], ],
"required": true, "required": true,
"description": "The status of the conversation" "description": "The status of the conversation"
@ -500,6 +552,18 @@
"operationId": "conversationNewMessage", "operationId": "conversationNewMessage",
"summary": "Create New Message", "summary": "Create New Message",
"description": "All the agent replies are created as new messages through this endpoint", "description": "All the agent replies are created as new messages through this endpoint",
"security": [
{
"userApiKey": [
]
},
{
"agentBotApiKey": [
]
}
],
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
@ -1035,6 +1099,14 @@
"type": "number", "type": "number",
"description": "ID of the inbox" "description": "ID of the inbox"
}, },
"name": {
"type": "string",
"description": "The name of the inbox"
},
"website_url": {
"type": "string",
"description": "Website URL"
},
"channel_type": { "channel_type": {
"type": "string", "type": "string",
"description": "The type of the inbox" "description": "The type of the inbox"