mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	feat: Add Public APIs for API Channel (#2375)
This commit is contained in:
		| @@ -54,6 +54,7 @@ Rails/ApplicationController: | |||||||
|     - 'app/controllers/widget_tests_controller.rb' |     - 'app/controllers/widget_tests_controller.rb' | ||||||
|     - 'app/controllers/widgets_controller.rb' |     - 'app/controllers/widgets_controller.rb' | ||||||
|     - 'app/controllers/platform_controller.rb' |     - 'app/controllers/platform_controller.rb' | ||||||
|  |     - 'app/controllers/public_controller.rb' | ||||||
| Style/ClassAndModuleChildren: | Style/ClassAndModuleChildren: | ||||||
|   EnforcedStyle: compact |   EnforcedStyle: compact | ||||||
|   Exclude: |   Exclude: | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| class ContactBuilder | class ContactBuilder | ||||||
|   pattr_initialize [:source_id!, :inbox!, :contact_attributes!] |   pattr_initialize [:source_id!, :inbox!, :contact_attributes!, :hmac_verified] | ||||||
|  |  | ||||||
|   def perform |   def perform | ||||||
|     contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id) |     contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id) | ||||||
| @@ -18,7 +18,8 @@ class ContactBuilder | |||||||
|     ::ContactInbox.create!( |     ::ContactInbox.create!( | ||||||
|       contact_id: contact.id, |       contact_id: contact.id, | ||||||
|       inbox_id: inbox.id, |       inbox_id: inbox.id, | ||||||
|       source_id: source_id |       source_id: source_id, | ||||||
|  |       hmac_verified: hmac_verified || false | ||||||
|     ) |     ) | ||||||
|   end |   end | ||||||
|  |  | ||||||
| @@ -28,7 +29,7 @@ class ContactBuilder | |||||||
|  |  | ||||||
|   def create_contact |   def create_contact | ||||||
|     account.contacts.create!( |     account.contacts.create!( | ||||||
|       name: contact_attributes[:name], |       name: contact_attributes[:name] || ::Haikunator.haikunate(1000), | ||||||
|       phone_number: contact_attributes[:phone_number], |       phone_number: contact_attributes[:phone_number], | ||||||
|       email: contact_attributes[:email], |       email: contact_attributes[:email], | ||||||
|       identifier: contact_attributes[:identifier], |       identifier: contact_attributes[:identifier], | ||||||
|   | |||||||
| @@ -58,6 +58,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def permitted_params |   def permitted_params | ||||||
|  |     # timestamp parameter is used in create conversation method | ||||||
|     params.permit(:id, :before, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id]) |     params.permit(:id, :before, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id]) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								app/controllers/public/api/v1/inboxes/contacts_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/controllers/public/api/v1/inboxes/contacts_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesController | ||||||
|  |   before_action :contact_inbox, except: [:create] | ||||||
|  |   before_action :process_hmac | ||||||
|  |  | ||||||
|  |   def create | ||||||
|  |     source_id = params[:source_id] || SecureRandom.uuid | ||||||
|  |     @contact_inbox = ::ContactBuilder.new( | ||||||
|  |       source_id: source_id, | ||||||
|  |       inbox: @inbox_channel.inbox, | ||||||
|  |       contact_attributes: permitted_params.except(:identifier, :identifier_hash) | ||||||
|  |     ).perform | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def show; end | ||||||
|  |  | ||||||
|  |   def update | ||||||
|  |     contact_identify_action = ContactIdentifyAction.new( | ||||||
|  |       contact: @contact_inbox.contact, | ||||||
|  |       params: permitted_params.to_h.deep_symbolize_keys.except(:identifier) | ||||||
|  |     ) | ||||||
|  |     render json: contact_identify_action.perform | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def contact_inbox | ||||||
|  |     @contact_inbox = @inbox_channel.inbox.contact_inboxes.find_by!(source_id: params[:id]) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def process_hmac | ||||||
|  |     return if params[:identifier_hash].blank? && !@inbox_channel.hmac_mandatory | ||||||
|  |     raise StandardError, 'HMAC failed: Invalid Identifier Hash Provided' unless valid_hmac? | ||||||
|  |  | ||||||
|  |     @contact_inbox.update(hmac_verified: true) if @contact_inbox.present? | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def valid_hmac? | ||||||
|  |     params[:identifier_hash] == OpenSSL::HMAC.hexdigest( | ||||||
|  |       'sha256', | ||||||
|  |       @inbox_channel.hmac_token, | ||||||
|  |       params[:identifier].to_s | ||||||
|  |     ) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def permitted_params | ||||||
|  |     params.permit(:identifier, :identifier_hash, :email, :name, :avatar_url, custom_attributes: {}) | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::InboxesController | ||||||
|  |   def index | ||||||
|  |     @conversations = @contact_inbox.hmac_verified? ? @contact.conversations : @contact_inbox.conversations | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def create | ||||||
|  |     @conversation = create_conversation | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def create_conversation | ||||||
|  |     ::Conversation.create!(conversation_params) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def conversation_params | ||||||
|  |     { | ||||||
|  |       account_id: @contact_inbox.contact.account_id, | ||||||
|  |       inbox_id: @contact_inbox.inbox_id, | ||||||
|  |       contact_id: @contact_inbox.contact_id, | ||||||
|  |       contact_inbox_id: @contact_inbox.id | ||||||
|  |     } | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										68
									
								
								app/controllers/public/api/v1/inboxes/messages_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								app/controllers/public/api/v1/inboxes/messages_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesController | ||||||
|  |   before_action :set_message, only: [:update] | ||||||
|  |  | ||||||
|  |   def index | ||||||
|  |     @messages = @conversation.nil? ? [] : message_finder.perform | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def create | ||||||
|  |     @message = @conversation.messages.new(message_params) | ||||||
|  |     @message.save | ||||||
|  |     build_attachment | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def update | ||||||
|  |     @message.update!(message_update_params) | ||||||
|  |   rescue StandardError => e | ||||||
|  |     render json: { error: @contact.errors, message: e.message }.to_json, status: 500 | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def build_attachment | ||||||
|  |     return if params[:attachments].blank? | ||||||
|  |  | ||||||
|  |     params[:attachments].each do |uploaded_attachment| | ||||||
|  |       attachment = @message.attachments.new( | ||||||
|  |         account_id: @message.account_id, | ||||||
|  |         file_type: helpers.file_type(uploaded_attachment&.content_type) | ||||||
|  |       ) | ||||||
|  |       attachment.file.attach(uploaded_attachment) | ||||||
|  |     end | ||||||
|  |     @message.save! | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def message_finder_params | ||||||
|  |     { | ||||||
|  |       filter_internal_messages: true, | ||||||
|  |       before: params[:before] | ||||||
|  |     } | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def message_finder | ||||||
|  |     @message_finder ||= MessageFinder.new(@conversation, message_finder_params) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def message_update_params | ||||||
|  |     params.permit(submitted_values: [:name, :title, :value]) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def permitted_params | ||||||
|  |     params.permit(:content, :echo_id) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def set_message | ||||||
|  |     @message = @conversation.messages.find(params[:id]) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def message_params | ||||||
|  |     { | ||||||
|  |       account_id: @conversation.account_id, | ||||||
|  |       sender: @contact_inbox.contact, | ||||||
|  |       content: permitted_params[:content], | ||||||
|  |       inbox_id: @conversation.inbox_id, | ||||||
|  |       echo_id: permitted_params[:echo_id], | ||||||
|  |       message_type: :incoming | ||||||
|  |     } | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										23
									
								
								app/controllers/public/api/v1/inboxes_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/controllers/public/api/v1/inboxes_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | class Public::Api::V1::InboxesController < PublicController | ||||||
|  |   before_action :set_inbox_channel | ||||||
|  |   before_action :set_contact_inbox | ||||||
|  |   before_action :set_conversation | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def set_inbox_channel | ||||||
|  |     @inbox_channel = ::Channel::Api.find_by!(identifier: params[:inbox_id]) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def set_contact_inbox | ||||||
|  |     return if params[:contact_id].blank? | ||||||
|  |  | ||||||
|  |     @contact_inbox = @inbox_channel.inbox.contact_inboxes.find_by!(source_id: params[:contact_id]) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def set_conversation | ||||||
|  |     return if params[:conversation_id].blank? | ||||||
|  |  | ||||||
|  |     @conversation = @contact_inbox.contact.conversations.find_by!(display_id: params[:conversation_id]) | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										3
									
								
								app/controllers/public_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/controllers/public_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | class PublicController < ActionController::Base | ||||||
|  |   skip_before_action :verify_authenticity_token | ||||||
|  | end | ||||||
| @@ -2,11 +2,19 @@ | |||||||
| # | # | ||||||
| # Table name: channel_api | # Table name: channel_api | ||||||
| # | # | ||||||
| #  id          :bigint           not null, primary key | #  id             :bigint           not null, primary key | ||||||
| #  webhook_url :string | #  hmac_mandatory :boolean          default(FALSE) | ||||||
| #  created_at  :datetime         not null | #  hmac_token     :string | ||||||
| #  updated_at  :datetime         not null | #  identifier     :string | ||||||
| #  account_id  :integer          not null | #  webhook_url    :string | ||||||
|  | #  created_at     :datetime         not null | ||||||
|  | #  updated_at     :datetime         not null | ||||||
|  | #  account_id     :integer          not null | ||||||
|  | # | ||||||
|  | # Indexes | ||||||
|  | # | ||||||
|  | #  index_channel_api_on_hmac_token  (hmac_token) UNIQUE | ||||||
|  | #  index_channel_api_on_identifier  (identifier) UNIQUE | ||||||
| # | # | ||||||
|  |  | ||||||
| class Channel::Api < ApplicationRecord | class Channel::Api < ApplicationRecord | ||||||
| @@ -15,6 +23,9 @@ class Channel::Api < ApplicationRecord | |||||||
|   validates :account_id, presence: true |   validates :account_id, presence: true | ||||||
|   belongs_to :account |   belongs_to :account | ||||||
|  |  | ||||||
|  |   has_secure_token :identifier | ||||||
|  |   has_secure_token :hmac_token | ||||||
|  |  | ||||||
|   has_one :inbox, as: :channel, dependent: :destroy |   has_one :inbox, as: :channel, dependent: :destroy | ||||||
|  |  | ||||||
|   def name |   def name | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| # | # | ||||||
| #  id                    :integer          not null, primary key | #  id                    :integer          not null, primary key | ||||||
| #  feature_flags         :integer          default(3), not null | #  feature_flags         :integer          default(3), not null | ||||||
|  | #  hmac_mandatory        :boolean          default(FALSE) | ||||||
| #  hmac_token            :string | #  hmac_token            :string | ||||||
| #  pre_chat_form_enabled :boolean          default(FALSE) | #  pre_chat_form_enabled :boolean          default(FALSE) | ||||||
| #  pre_chat_form_options :jsonb | #  pre_chat_form_options :jsonb | ||||||
|   | |||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | json.source_id @contact_inbox.source_id | ||||||
|  | json.partial! 'public/api/v1/models/contact.json.jbuilder', resource: @contact_inbox.contact | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | json.source_id @contact_inbox.source_id | ||||||
|  | json.partial! 'public/api/v1/models/contact.json.jbuilder', resource: @contact_inbox.contact | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | json.source_id @contact_inbox.source_id | ||||||
|  | json.partial! 'public/api/v1/models/contact.json.jbuilder', resource: @contact_inbox.contact | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | json.partial! 'public/api/v1/models/conversation.json.jbuilder', resource: @conversation | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | json.array! @conversations do |conversation| | ||||||
|  |   json.partial! 'public/api/v1/models/conversation.json.jbuilder', resource: conversation | ||||||
|  | end | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | json.partial! 'public/api/v1/models/message.json.jbuilder', resource: @message | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | json.array! @messages do |message| | ||||||
|  |   json.partial! 'public/api/v1/models/message.json.jbuilder', resource: message | ||||||
|  | end | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | json.partial! 'public/api/v1/models/message.json.jbuilder', resource: @message | ||||||
							
								
								
									
										4
									
								
								app/views/public/api/v1/models/_contact.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/views/public/api/v1/models/_contact.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | json.id resource.id | ||||||
|  | json.name resource.name | ||||||
|  | json.email resource.email | ||||||
|  | json.pubsub_token resource.pubsub_token | ||||||
							
								
								
									
										10
									
								
								app/views/public/api/v1/models/_conversation.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/views/public/api/v1/models/_conversation.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | json.id resource.display_id | ||||||
|  | json.inbox_id resource.inbox_id | ||||||
|  | json.contact_last_seen_at resource.contact_last_seen_at.to_i | ||||||
|  | json.status resource.status | ||||||
|  | json.messages do | ||||||
|  |   json.array! resource.messages do |message| | ||||||
|  |     json.partial! 'public/api/v1/models/message.json.jbuilder', resource: message | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | json.contact resource.contact | ||||||
							
								
								
									
										9
									
								
								app/views/public/api/v1/models/_message.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/views/public/api/v1/models/_message.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | json.id resource.id | ||||||
|  | json.content resource.content | ||||||
|  | json.message_type resource.message_type_before_type_cast | ||||||
|  | json.content_type resource.content_type | ||||||
|  | json.content_attributes resource.content_attributes | ||||||
|  | json.created_at resource.created_at.to_i | ||||||
|  | json.conversation_id resource.conversation.display_id | ||||||
|  | json.attachments resource.attachments.map(&:push_event_data) if resource.attachments.present? | ||||||
|  | json.sender resource.sender.push_event_data if resource.sender | ||||||
| @@ -85,4 +85,7 @@ Rails.application.configure do | |||||||
|       resource '*', headers: :any, methods: :any, expose: ['access-token', 'client', 'uid', 'expiry'] |       resource '*', headers: :any, methods: :any, expose: ['access-token', 'client', 'uid', 'expiry'] | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   # ref : https://medium.com/@emikaijuin/connecting-to-action-cable-without-rails-d39a8aaa52d5 | ||||||
|  |   config.action_cable.disable_request_forgery_protection = true | ||||||
| end | end | ||||||
|   | |||||||
| @@ -42,9 +42,12 @@ Rails.application.configure do | |||||||
|   # Mount Action Cable outside main process or domain |   # Mount Action Cable outside main process or domain | ||||||
|   # config.action_cable.mount_path = nil |   # config.action_cable.mount_path = nil | ||||||
|   # config.action_cable.url = 'wss://example.com/cable' |   # config.action_cable.url = 'wss://example.com/cable' | ||||||
|   if ENV['FRONTEND_URL'].present? |  | ||||||
|     config.action_cable.allowed_request_origins = [ENV['FRONTEND_URL'], %r{https?://#{URI.parse(ENV['FRONTEND_URL']).host}(:[0-9]+)?}] |   # to enable connecting to the API channel public APIs | ||||||
|   end |   config.action_cable.disable_request_forgery_protection = true | ||||||
|  |   # if ENV['FRONTEND_URL'].present? | ||||||
|  |   #   config.action_cable.allowed_request_origins = [ENV['FRONTEND_URL'], %r{https?://#{URI.parse(ENV['FRONTEND_URL']).host}(:[0-9]+)?}] | ||||||
|  |   # end | ||||||
|  |  | ||||||
|   # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. |   # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. | ||||||
|   config.force_ssl = ActiveModel::Type::Boolean.new.cast(ENV.fetch('FORCE_SSL', false)) |   config.force_ssl = ActiveModel::Type::Boolean.new.cast(ENV.fetch('FORCE_SSL', false)) | ||||||
|   | |||||||
| @@ -199,6 +199,24 @@ Rails.application.routes.draw do | |||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   # ---------------------------------------------------------------------- | ||||||
|  |   # Routes for inbox APIs Exposed to contacts | ||||||
|  |   namespace :public, defaults: { format: 'json' } do | ||||||
|  |     namespace :api do | ||||||
|  |       namespace :v1 do | ||||||
|  |         resources :inboxes do | ||||||
|  |           scope module: :inboxes do | ||||||
|  |             resources :contacts, only: [:create, :show, :update] do | ||||||
|  |               resources :conversations, only: [:index, :create] do | ||||||
|  |                 resources :messages, only: [:index, :create, :update] | ||||||
|  |               end | ||||||
|  |             end | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|   # ---------------------------------------------------------------------- |   # ---------------------------------------------------------------------- | ||||||
|   # Used in mailer templates |   # Used in mailer templates | ||||||
|   resource :app, only: [:index] do |   resource :app, only: [:index] do | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								db/migrate/20210602182058_add_hmac_to_api_channel.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								db/migrate/20210602182058_add_hmac_to_api_channel.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | class AddHmacToApiChannel < ActiveRecord::Migration[6.0] | ||||||
|  |   def change | ||||||
|  |     add_column :channel_api, :identifier, :string | ||||||
|  |     add_index :channel_api, :identifier, unique: true | ||||||
|  |     add_column :channel_api, :hmac_token, :string | ||||||
|  |     add_index :channel_api, :hmac_token, unique: true | ||||||
|  |     add_column :channel_api, :hmac_mandatory, :boolean, default: false | ||||||
|  |     add_column :channel_web_widgets, :hmac_mandatory, :boolean, default: false | ||||||
|  |     set_up_existing_api_channels | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def set_up_existing_api_channels | ||||||
|  |     ::Channel::Api.find_in_batches do |api_channels_batch| | ||||||
|  |       Rails.logger.info "migrated till #{api_channels_batch.first.id}\n" | ||||||
|  |       api_channels_batch.map(&:regenerate_hmac_token) | ||||||
|  |       api_channels_batch.map(&:regenerate_identifier) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -143,6 +143,11 @@ ActiveRecord::Schema.define(version: 2021_06_09_133433) do | |||||||
|     t.string "webhook_url" |     t.string "webhook_url" | ||||||
|     t.datetime "created_at", precision: 6, null: false |     t.datetime "created_at", precision: 6, null: false | ||||||
|     t.datetime "updated_at", precision: 6, null: false |     t.datetime "updated_at", precision: 6, null: false | ||||||
|  |     t.string "identifier" | ||||||
|  |     t.string "hmac_token" | ||||||
|  |     t.boolean "hmac_mandatory", default: false | ||||||
|  |     t.index ["hmac_token"], name: "index_channel_api_on_hmac_token", unique: true | ||||||
|  |     t.index ["identifier"], name: "index_channel_api_on_identifier", unique: true | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   create_table "channel_email", force: :cascade do |t| |   create_table "channel_email", force: :cascade do |t| | ||||||
| @@ -201,6 +206,7 @@ ActiveRecord::Schema.define(version: 2021_06_09_133433) do | |||||||
|     t.string "hmac_token" |     t.string "hmac_token" | ||||||
|     t.boolean "pre_chat_form_enabled", default: false |     t.boolean "pre_chat_form_enabled", default: false | ||||||
|     t.jsonb "pre_chat_form_options", default: {} |     t.jsonb "pre_chat_form_options", default: {} | ||||||
|  |     t.boolean "hmac_mandatory", default: false | ||||||
|     t.index ["hmac_token"], name: "index_channel_web_widgets_on_hmac_token", unique: true |     t.index ["hmac_token"], name: "index_channel_web_widgets_on_hmac_token", unique: true | ||||||
|     t.index ["website_token"], name: "index_channel_web_widgets_on_website_token", unique: true |     t.index ["website_token"], name: "index_channel_web_widgets_on_website_token", unique: true | ||||||
|   end |   end | ||||||
|   | |||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | RSpec.describe 'Public Inbox Contacts API', type: :request do | ||||||
|  |   let!(:api_channel) { create(:channel_api) } | ||||||
|  |   let!(:contact) { create(:contact) } | ||||||
|  |   let!(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: api_channel.inbox) } | ||||||
|  |  | ||||||
|  |   describe 'POST /public/api/v1/inboxes/{identifier}/contact' do | ||||||
|  |     it 'creates a contact and return the source id' do | ||||||
|  |       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts" | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data['source_id']).not_to eq nil | ||||||
|  |       expect(data['pubsub_token']).not_to eq nil | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   describe 'GET /public/api/v1/inboxes/{identifier}/contact/{source_id}' do | ||||||
|  |     it 'gets a contact when present' do | ||||||
|  |       get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}" | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data['source_id']).to eq contact_inbox.source_id | ||||||
|  |       expect(data['pubsub_token']).to eq contact.pubsub_token | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   describe 'PATCH /public/api/v1/inboxes/{identifier}/contact/{source_id}' do | ||||||
|  |     it 'updates a contact when present' do | ||||||
|  |       patch "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}", | ||||||
|  |             params: { name: 'John Smith' } | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data['name']).to eq 'John Smith' | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | RSpec.describe 'Public Inbox Contact Conversations API', type: :request do | ||||||
|  |   let!(:api_channel) { create(:channel_api) } | ||||||
|  |   let!(:contact) { create(:contact) } | ||||||
|  |   let!(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: api_channel.inbox) } | ||||||
|  |  | ||||||
|  |   describe 'GET /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations' do | ||||||
|  |     it 'return the conversations for that contact' do | ||||||
|  |       create(:conversation, contact_inbox: contact_inbox) | ||||||
|  |       get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations" | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data.length).to eq 1 | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations' do | ||||||
|  |     it 'creates a conversation for that contact' do | ||||||
|  |       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations" | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data['id']).not_to eq nil | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -0,0 +1,57 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | RSpec.describe 'Public Inbox Contact Conversation Messages API', type: :request do | ||||||
|  |   let!(:api_channel) { create(:channel_api) } | ||||||
|  |   let!(:contact) { create(:contact) } | ||||||
|  |   let!(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: api_channel.inbox) } | ||||||
|  |   let!(:conversation)  { create(:conversation, contact: contact, contact_inbox: contact_inbox) } | ||||||
|  |  | ||||||
|  |   describe 'GET /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}/messages' do | ||||||
|  |     it 'return the messages for that conversation' do | ||||||
|  |       2.times.each { create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation) } | ||||||
|  |  | ||||||
|  |       get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}/messages" | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data.length).to eq 2 | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}/messages' do | ||||||
|  |     it 'creates a message in the conversation' do | ||||||
|  |       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}/messages", | ||||||
|  |            params: { content: 'hello' } | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data['content']).to eq('hello') | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     it 'creates attachment message in conversation' do | ||||||
|  |       file = fixture_file_upload(Rails.root.join('spec/assets/avatar.png'), 'image/png') | ||||||
|  |       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}/messages", | ||||||
|  |            params: { content: 'hello', attachments: [file] } | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data['content']).to eq('hello') | ||||||
|  |  | ||||||
|  |       expect(conversation.messages.last.attachments.first.file.present?).to eq(true) | ||||||
|  |       expect(conversation.messages.last.attachments.first.file_type).to eq('image') | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   describe 'PATCH /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}/messages/{id}' do | ||||||
|  |     it 'creates a message in the conversation' do | ||||||
|  |       message = create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation) | ||||||
|  |       patch "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/" \ | ||||||
|  |             "#{conversation.display_id}/messages/#{message.id}", | ||||||
|  |             params: { submitted_values: [{ title: 'test' }] } | ||||||
|  |  | ||||||
|  |       expect(response).to have_http_status(:success) | ||||||
|  |       data = JSON.parse(response.body) | ||||||
|  |       expect(data['content_attributes']['submitted_values'].first['title']).to eq 'test' | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose