From 4f83d5451ebfa4eac865588f5b1b8c07e2201bf9 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 25 Jun 2020 23:35:16 +0530 Subject: [PATCH] Chore: Routine Bugfixes and enhancements (#979) - Fix slack scopes - Docs for authentication Fixes: #704 , #973 --- .circleci/config.yml | 2 +- Gemfile.lock | 36 ++++----- .../api/v1/accounts/labels_controller.rb | 2 +- app/controllers/application_controller.rb | 12 ++- app/controllers/widgets_controller.rb | 5 ++ app/finders/conversation_finder.rb | 2 +- .../dashboard/settings/labels/validations.js | 2 +- app/jobs/hook_job.rb | 2 +- app/models/message.rb | 6 +- app/services/base/send_on_channel_service.rb | 55 ++++++++++++++ ...service.rb => send_on_facebook_service.rb} | 33 ++------- ...e_service.rb => send_on_twilio_service.rb} | 19 ++--- ..._service.rb => send_on_twitter_service.rb} | 32 +++----- .../partials/_conversation.json.jbuilder | 2 +- config/application.rb | 4 - config/initializers/redis.rb | 4 + config/integration/apps.yml | 2 +- .../project-setup/slack-integration-setup.md | 2 +- lib/integrations/slack/channel_builder.rb | 8 +- .../slack/incoming_message_builder.rb | 5 +- ...ge_builder.rb => send_on_slack_service.rb} | 44 +++++------ .../integrations/apps_controller_spec.rb | 9 ++- ..._spec.rb => send_on_slack_service_spec.rb} | 4 +- ...ec.rb => send_on_facebook_service_spec.rb} | 2 +- ...spec.rb => send_on_twilio_service_spec.rb} | 2 +- ...pec.rb => send_on_twitter_service_spec.rb} | 2 +- spec/spec_helper.rb | 2 - swagger/index.yml | 18 +++++ .../paths/conversation/index_or_create.yml | 3 + .../conversation/messages/index_create.yml | 3 + swagger/paths/conversation/toggle_status.yml | 3 + swagger/swagger.json | 74 ++++++++++++++++++- 32 files changed, 254 insertions(+), 147 deletions(-) create mode 100644 app/services/base/send_on_channel_service.rb rename app/services/facebook/{send_reply_service.rb => send_on_facebook_service.rb} (69%) rename app/services/twilio/{outgoing_message_service.rb => send_on_twilio_service.rb} (65%) rename app/services/twitter/{send_reply_service.rb => send_on_twitter_service.rb} (71%) rename lib/integrations/slack/{outgoing_message_builder.rb => send_on_slack_service.rb} (51%) rename spec/lib/integrations/slack/{outgoing_message_builder_spec.rb => send_on_slack_service_spec.rb} (89%) rename spec/services/facebook/{send_reply_service_spec.rb => send_on_facebook_service_spec.rb} (98%) rename spec/services/twilio/{outgoing_message_service_spec.rb => send_on_twilio_service_spec.rb} (98%) rename spec/services/twitter/{send_reply_service_spec.rb => send_on_twitter_service_spec.rb} (98%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 07f4a7991..18a7a0af9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -98,7 +98,7 @@ jobs: - run: name: Run backend tests 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 - persist_to_workspace: root: ~/tmp diff --git a/Gemfile.lock b/Gemfile.lock index a71da76b2..40e05f084 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -93,10 +93,10 @@ GEM rake (>= 10.4, < 14.0) ast (2.4.1) attr_extras (6.2.4) - autoprefixer-rails (9.7.6) + autoprefixer-rails (9.8.2) execjs aws-eventstream (1.1.0) - aws-partitions (1.329.0) + aws-partitions (1.332.0) aws-sdk-core (3.100.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) @@ -105,12 +105,12 @@ GEM aws-sdk-kms (1.34.1) aws-sdk-core (~> 3, >= 3.99.0) 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-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.4) - aws-eventstream (~> 1.0, >= 1.0.2) + aws-sigv4 (1.2.0) + aws-eventstream (~> 1, >= 1.0.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) @@ -163,7 +163,7 @@ GEM devise (> 3.5.2, < 5) rails (>= 4.2.0, < 6.1) sprockets (= 3.7.2) - diff-lcs (1.3) + diff-lcs (1.4) digest-crc (0.5.1) docile (1.3.2) domain_name (0.5.20190701) @@ -178,11 +178,11 @@ GEM facebook-messenger (1.5.0) httparty (~> 0.13, >= 0.13.7) rack (>= 1.4.5) - factory_bot (5.2.0) - activesupport (>= 4.2.0) - factory_bot_rails (5.2.0) - factory_bot (~> 5.2.0) - railties (>= 4.2.0) + factory_bot (6.0.2) + activesupport (>= 5.0.0) + factory_bot_rails (6.0.0) + factory_bot (~> 6.0.0) + railties (>= 5.0.0) faker (2.12.0) i18n (>= 1.6, < 2) faraday (1.0.1) @@ -197,7 +197,7 @@ GEM gli (2.19.1) globalid (0.4.2) activesupport (>= 4.2.0) - google-api-client (0.40.2) + google-api-client (0.41.0) addressable (~> 2.5, >= 2.5.1) googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) @@ -218,7 +218,7 @@ GEM google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.12.0) + googleauth (0.13.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -306,8 +306,8 @@ GEM orm_adapter (0.5.0) os (1.1.0) parallel (1.19.2) - parser (2.7.1.3) - ast (~> 2.4.0) + parser (2.7.1.4) + ast (~> 2.4.1) pg (1.2.3) pry (0.13.1) coderay (~> 1.1) @@ -367,7 +367,7 @@ GEM redis-rack-cache (2.2.1) rack-cache (>= 1.10, < 2) redis-store (>= 1.6, < 2) - redis-store (1.8.2) + redis-store (1.9.0) redis (>= 4, < 5) regexp_parser (1.7.1) representable (3.0.4) @@ -401,13 +401,13 @@ GEM rspec-mocks (~> 3.9) rspec-support (~> 3.9) rspec-support (3.9.3) - rubocop (0.85.1) + rubocop (0.86.0) parallel (~> 1.10) parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.7) rexml - rubocop-ast (>= 0.0.3) + rubocop-ast (>= 0.0.3, < 1.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) rubocop-ast (0.0.3) diff --git a/app/controllers/api/v1/accounts/labels_controller.rb b/app/controllers/api/v1/accounts/labels_controller.rb index 12f0d404a..12c026e66 100644 --- a/app/controllers/api/v1/accounts/labels_controller.rb +++ b/app/controllers/api/v1/accounts/labels_controller.rb @@ -14,7 +14,7 @@ class Api::V1::Accounts::LabelsController < Api::V1::Accounts::BaseController end def update - @label.update(permitted_params) + @label.update!(permitted_params) end def destroy diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4b22c8d32..561a797c3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -59,11 +59,19 @@ class ApplicationController < ActionController::Base render json: exception.to_hash, status: exception.http_status 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) # 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 - locale ||= (I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil) + locale ||= locale_from_account(account) I18n.locale = locale || I18n.default_locale end diff --git a/app/controllers/widgets_controller.rb b/app/controllers/widgets_controller.rb index 4e784ac60..c396de4ce 100644 --- a/app/controllers/widgets_controller.rb +++ b/app/controllers/widgets_controller.rb @@ -4,6 +4,7 @@ class WidgetsController < ActionController::Base before_action :set_token before_action :set_contact before_action :build_contact + after_action :allow_iframe_requests def index; end @@ -50,4 +51,8 @@ class WidgetsController < ActionController::Base def permitted_params params.permit(:website_token, :cw_conversation) end + + def allow_iframe_requests + response.headers.delete('X-Frame-Options') + end end diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index fcc0daf98..495bddf63 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -62,7 +62,7 @@ class ConversationFinder def find_all_conversations @conversations = current_account.conversations.includes( - :assignee, :contact, :inbox + :assignee, :inbox, contact: [:avatar_attachment] ).where(inbox_id: @inbox_ids) end diff --git a/app/javascript/dashboard/routes/dashboard/settings/labels/validations.js b/app/javascript/dashboard/routes/dashboard/settings/labels/validations.js index aacd9c6eb..0a2373a06 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/labels/validations.js +++ b/app/javascript/dashboard/routes/dashboard/settings/labels/validations.js @@ -1,6 +1,6 @@ import { required, minLength } from 'vuelidate/lib/validators'; -export const validLabelCharacters = (str = '') => /^[\w-_]+$/g.test(str); +export const validLabelCharacters = (str = '') => !!str && !str.includes(' '); export default { title: { diff --git a/app/jobs/hook_job.rb b/app/jobs/hook_job.rb index f867248d8..6fc8934d2 100644 --- a/app/jobs/hook_job.rb +++ b/app/jobs/hook_job.rb @@ -4,7 +4,7 @@ class HookJob < ApplicationJob def perform(hook, message) return unless hook.slack? - Integrations::Slack::OutgoingMessageBuilder.perform(hook, message) + Integrations::Slack::SendOnSlackService.new(message: message, hook: hook).perform rescue StandardError => e Raven.capture_exception(e) end diff --git a/app/models/message.rb b/app/models/message.rb index b8678ea0a..526131098 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -138,11 +138,11 @@ class Message < ApplicationRecord def send_reply channel_name = conversation.inbox.channel.class.to_s if channel_name == 'Channel::FacebookPage' - ::Facebook::SendReplyService.new(message: self).perform + ::Facebook::SendOnFacebookService.new(message: self).perform elsif channel_name == 'Channel::TwitterProfile' - ::Twitter::SendReplyService.new(message: self).perform + ::Twitter::SendOnTwitterService.new(message: self).perform elsif channel_name == 'Channel::TwilioSms' - ::Twilio::OutgoingMessageService.new(message: self).perform + ::Twilio::SendOnTwilioService.new(message: self).perform end end diff --git a/app/services/base/send_on_channel_service.rb b/app/services/base/send_on_channel_service.rb new file mode 100644 index 000000000..2aeb2df09 --- /dev/null +++ b/app/services/base/send_on_channel_service.rb @@ -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 diff --git a/app/services/facebook/send_reply_service.rb b/app/services/facebook/send_on_facebook_service.rb similarity index 69% rename from app/services/facebook/send_reply_service.rb rename to app/services/facebook/send_on_facebook_service.rb index f80f329fd..d27dfcf4d 100644 --- a/app/services/facebook/send_reply_service.rb +++ b/app/services/facebook/send_on_facebook_service.rb @@ -1,37 +1,14 @@ -class Facebook::SendReplyService - 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 - +class Facebook::SendOnFacebookService < Base::SendOnChannelService private - delegate :contact, to: :conversation - - def inbox - @inbox ||= message.inbox + def channel_class + Channel::FacebookPage end - def conversation - @conversation ||= message.conversation + def perform_reply + FacebookBot::Bot.deliver(delivery_params, access_token: message.channel_token) 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 { recipient: { id: contact.get_source_id(inbox.id) }, diff --git a/app/services/twilio/outgoing_message_service.rb b/app/services/twilio/send_on_twilio_service.rb similarity index 65% rename from app/services/twilio/outgoing_message_service.rb rename to app/services/twilio/send_on_twilio_service.rb index 6c4371d76..684eb31fe 100644 --- a/app/services/twilio/outgoing_message_service.rb +++ b/app/services/twilio/send_on_twilio_service.rb @@ -1,22 +1,15 @@ -class Twilio::OutgoingMessageService - pattr_initialize [:message!] +class Twilio::SendOnTwilioService < Base::SendOnChannelService + private - def perform - return if message.private - return if message.source_id - return if inbox.channel.class.to_s != 'Channel::TwilioSms' - return unless outgoing_message? + def channel_class + Channel::TwilioSms + end + def perform_reply twilio_message = client.messages.create(message_params) message.update!(source_id: twilio_message.sid) end - private - - delegate :conversation, to: :message - delegate :contact, to: :conversation - delegate :contact_inbox, to: :conversation - def message_params params = { body: message.content, diff --git a/app/services/twitter/send_reply_service.rb b/app/services/twitter/send_on_twitter_service.rb similarity index 71% rename from app/services/twitter/send_reply_service.rb rename to app/services/twitter/send_on_twitter_service.rb index b68b3aef6..93088fb41 100644 --- a/app/services/twitter/send_reply_service.rb +++ b/app/services/twitter/send_on_twitter_service.rb @@ -1,16 +1,17 @@ -class Twitter::SendReplyService +class Twitter::SendOnTwitterService < Base::SendOnChannelService pattr_initialize [:message!] - def perform - return if message.private - return if message.source_id - return if inbox.channel.class.to_s != 'Channel::TwitterProfile' - return unless outgoing_message_from_chatwoot? + private - send_reply + delegate :additional_attributes, to: :contact + + def channel_class + Channel::TwitterProfile end - private + def perform_reply + conversation_type == 'tweet' ? send_tweet_reply : send_direct_message + end def twitter_client Twitty::Facade.new do |config| @@ -50,19 +51,4 @@ class Twitter::SendReplyService Rails.logger.info 'TWITTER_TWEET_REPLY_ERROR' + response.body 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 diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index e0be1e610..187348b38 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -12,7 +12,7 @@ json.id conversation.display_id 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) + json.messages conversation.unread_messages.includes([:user, :attachments]).map(&:push_event_data) end json.inbox_id conversation.inbox_id diff --git a/config/application.rb b/config/application.rb index 436a9392f..29943a011 100644 --- a/config/application.rb +++ b/config/application.rb @@ -27,10 +27,6 @@ module Chatwoot config.generators.javascripts = false config.generators.stylesheets = false - config.action_dispatch.default_headers = { - 'X-Frame-Options' => 'ALLOWALL' - } - # Custom chatwoot configurations config.x = config_for(:app).with_indifferent_access end diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb index d893b281c..c059323aa 100644 --- a/config/initializers/redis.rb +++ b/config/initializers/redis.rb @@ -7,3 +7,7 @@ redis = Rails.env.test? ? MockRedis.new : Redis.new(app_redis_config) # Alfred - Used currently for round robin and conversation emails. # Add here as you use it for more features $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 diff --git a/config/integration/apps.yml b/config/integration/apps.yml index 30397e6bb..a63b5f606 100644 --- a/config/integration/apps.yml +++ b/config/integration/apps.yml @@ -3,7 +3,7 @@ slack: name: Slack 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." - 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: id: webhook name: Webhooks diff --git a/docs/development/project-setup/slack-integration-setup.md b/docs/development/project-setup/slack-integration-setup.md index 010b9ff01..492effaa7 100644 --- a/docs/development/project-setup/slack-integration-setup.md +++ b/docs/development/project-setup/slack-integration-setup.md @@ -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. 4) In the redirect URLs, Add your Chatwoot installation base url. 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. 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` diff --git a/lib/integrations/slack/channel_builder.rb b/lib/integrations/slack/channel_builder.rb index 79e167598..aa2aa992a 100644 --- a/lib/integrations/slack/channel_builder.rb +++ b/lib/integrations/slack/channel_builder.rb @@ -17,10 +17,7 @@ class Integrations::Slack::ChannelBuilder end def slack_client - Slack.configure do |config| - config.token = hook.access_token - end - Slack::Web::Client.new + @slack_client ||= Slack::Web::Client.new(token: hook.access_token) end def find_or_create_channel @@ -29,6 +26,7 @@ class Integrations::Slack::ChannelBuilder end 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 diff --git a/lib/integrations/slack/incoming_message_builder.rb b/lib/integrations/slack/incoming_message_builder.rb index 3b46db3d8..ab414d21f 100644 --- a/lib/integrations/slack/incoming_message_builder.rb +++ b/lib/integrations/slack/incoming_message_builder.rb @@ -89,9 +89,6 @@ class Integrations::Slack::IncomingMessageBuilder end def slack_client - Slack.configure do |config| - config.token = integration_hook.access_token - end - Slack::Web::Client.new + @slack_client ||= Slack::Web::Client.new(token: @integration_hook.access_token) end end diff --git a/lib/integrations/slack/outgoing_message_builder.rb b/lib/integrations/slack/send_on_slack_service.rb similarity index 51% rename from lib/integrations/slack/outgoing_message_builder.rb rename to lib/integrations/slack/send_on_slack_service.rb index d3bbfb952..2a61e13f6 100644 --- a/lib/integrations/slack/outgoing_message_builder.rb +++ b/lib/integrations/slack/send_on_slack_service.rb @@ -1,30 +1,23 @@ -class Integrations::Slack::OutgoingMessageBuilder - attr_reader :hook, :message - - def self.perform(hook, message) - new(hook, message).perform - end - - def initialize(hook, message) - @hook = hook - @message = message - end +class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService + pattr_initialize [:message!, :hook!] 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 - update_reference_id + perform_reply end private - def conversation - @conversation ||= message.conversation - end - - def contact - @contact ||= conversation.contact + def perform_reply + send_message + update_reference_id end def agent @@ -32,8 +25,9 @@ class Integrations::Slack::OutgoingMessageBuilder end def message_content + private_indicator = message.private? ? 'private: ' : '' if conversation.identifier.present? - message.content + "#{private_indicator}#{message.content}" else "*Inbox: #{message.inbox.name}* \n\n #{message.content}" end @@ -59,14 +53,10 @@ class Integrations::Slack::OutgoingMessageBuilder def update_reference_id return if conversation.identifier - conversation.identifier = @slack_message['ts'] - conversation.save! + conversation.update!(identifier: @slack_message['ts']) end def slack_client - Slack.configure do |config| - config.token = hook.access_token - end - Slack::Web::Client.new + @slack_client ||= Slack::Web::Client.new(token: hook.access_token) end end diff --git a/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb b/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb index 37b6dc254..b1ac377bb 100644 --- a/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/integrations/apps_controller_spec.rb @@ -14,15 +14,16 @@ RSpec.describe 'Integration Apps API', type: :request do context 'when it is an authenticated user' do 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), headers: agent.create_new_auth_token, as: :json expect(response).to have_http_status(:success) - app = JSON.parse(response.body)['payload'].first - expect(app['id']).to eql('webhook') - expect(app['name']).to eql('Webhooks') + apps = JSON.parse(response.body)['payload'].first + expect(apps['id']).to eql(first_app.id) + expect(apps['name']).to eql(first_app.name) end end end diff --git a/spec/lib/integrations/slack/outgoing_message_builder_spec.rb b/spec/lib/integrations/slack/send_on_slack_service_spec.rb similarity index 89% rename from spec/lib/integrations/slack/outgoing_message_builder_spec.rb rename to spec/lib/integrations/slack/send_on_slack_service_spec.rb index 47fd5ba41..bf083a72a 100644 --- a/spec/lib/integrations/slack/outgoing_message_builder_spec.rb +++ b/spec/lib/integrations/slack/send_on_slack_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe Integrations::Slack::OutgoingMessageBuilder do +describe Integrations::Slack::SendOnSlackService do let(:account) { create(:account) } let!(:inbox) { create(:inbox, account: account) } let!(:contact) { create(:contact) } @@ -11,7 +11,7 @@ describe Integrations::Slack::OutgoingMessageBuilder do describe '#perform' 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') .to_return(status: 200, body: '', headers: {}) slack_client = double diff --git a/spec/services/facebook/send_reply_service_spec.rb b/spec/services/facebook/send_on_facebook_service_spec.rb similarity index 98% rename from spec/services/facebook/send_reply_service_spec.rb rename to spec/services/facebook/send_on_facebook_service_spec.rb index a74f1e826..742044abd 100644 --- a/spec/services/facebook/send_reply_service_spec.rb +++ b/spec/services/facebook/send_on_facebook_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe Facebook::SendReplyService do +describe Facebook::SendOnFacebookService do subject(:send_reply_service) { described_class.new(message: message) } before do diff --git a/spec/services/twilio/outgoing_message_service_spec.rb b/spec/services/twilio/send_on_twilio_service_spec.rb similarity index 98% rename from spec/services/twilio/outgoing_message_service_spec.rb rename to spec/services/twilio/send_on_twilio_service_spec.rb index 064f3bced..56a8283c3 100644 --- a/spec/services/twilio/outgoing_message_service_spec.rb +++ b/spec/services/twilio/send_on_twilio_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe Twilio::OutgoingMessageService do +describe Twilio::SendOnTwilioService do subject(:outgoing_message_service) { described_class.new(message: message) } let(:twilio_client) { instance_double(::Twilio::REST::Client) } diff --git a/spec/services/twitter/send_reply_service_spec.rb b/spec/services/twitter/send_on_twitter_service_spec.rb similarity index 98% rename from spec/services/twitter/send_reply_service_spec.rb rename to spec/services/twitter/send_on_twitter_service_spec.rb index 140144e83..017fcf27c 100644 --- a/spec/services/twitter/send_reply_service_spec.rb +++ b/spec/services/twitter/send_on_twitter_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe Twitter::SendReplyService do +describe Twitter::SendOnTwitterService do subject(:send_reply_service) { described_class.new(message: message) } let(:twitter_client) { instance_double(::Twitty::Facade) } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 207f3bea6..d017eb0da 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,4 @@ RSpec.configure do |config| end config.shared_context_metadata_behavior = :apply_to_host_groups - - # config.include Rails.application.routes.url_helpers end diff --git a/swagger/index.yml b/swagger/index.yml index 5fd289f37..b00c78da7 100644 --- a/swagger/index.yml +++ b/swagger/index.yml @@ -17,6 +17,24 @@ produces: - application/json; charset=utf-8 consumes: - 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: $ref: ./paths/index.yml diff --git a/swagger/paths/conversation/index_or_create.yml b/swagger/paths/conversation/index_or_create.yml index b1a2dbdd4..2afba74c8 100644 --- a/swagger/paths/conversation/index_or_create.yml +++ b/swagger/paths/conversation/index_or_create.yml @@ -45,6 +45,9 @@ post: operationId: newConversation summary: Create New Conversation description: Create conversation + security: + - userApiKey: [] + - agentBotApiKey: [] parameters: - name: data in: body diff --git a/swagger/paths/conversation/messages/index_create.yml b/swagger/paths/conversation/messages/index_create.yml index a1f9e3279..d7ccf44fe 100644 --- a/swagger/paths/conversation/messages/index_create.yml +++ b/swagger/paths/conversation/messages/index_create.yml @@ -30,6 +30,9 @@ post: operationId: conversationNewMessage summary: Create New Message description: All the agent replies are created as new messages through this endpoint + security: + - userApiKey: [] + - agentBotApiKey: [] parameters: - name: id in: path diff --git a/swagger/paths/conversation/toggle_status.yml b/swagger/paths/conversation/toggle_status.yml index e9b6be822..3ec5b43ba 100644 --- a/swagger/paths/conversation/toggle_status.yml +++ b/swagger/paths/conversation/toggle_status.yml @@ -4,6 +4,9 @@ post: operationId: conversationToggleStatus summary: Toggle Status description: Toggles the status of the conversation between open and resolved + security: + - userApiKey: [] + - agentBotApiKey: [] parameters: - name: id in: path diff --git a/swagger/swagger.json b/swagger/swagger.json index 50a255d32..2ac8cc1f2 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -24,6 +24,33 @@ "consumes": [ "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": { "/accounts/{account_id}/inboxes": { "post": { @@ -325,6 +352,18 @@ "operationId": "newConversation", "summary": "Create New Conversation", "description": "Create conversation", + "security": [ + { + "userApiKey": [ + + ] + }, + { + "agentBotApiKey": [ + + ] + } + ], "parameters": [ { "name": "data", @@ -409,6 +448,18 @@ "operationId": "conversationToggleStatus", "summary": "Toggle Status", "description": "Toggles the status of the conversation between open and resolved", + "security": [ + { + "userApiKey": [ + + ] + }, + { + "agentBotApiKey": [ + + ] + } + ], "parameters": [ { "name": "id", @@ -428,7 +479,8 @@ "type": "string", "enum": [ "open", - "resolved" + "resolved", + "bot" ], "required": true, "description": "The status of the conversation" @@ -500,6 +552,18 @@ "operationId": "conversationNewMessage", "summary": "Create New Message", "description": "All the agent replies are created as new messages through this endpoint", + "security": [ + { + "userApiKey": [ + + ] + }, + { + "agentBotApiKey": [ + + ] + } + ], "parameters": [ { "name": "id", @@ -1035,6 +1099,14 @@ "type": "number", "description": "ID of the inbox" }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "website_url": { + "type": "string", + "description": "Website URL" + }, "channel_type": { "type": "string", "description": "The type of the inbox"