From 853db60f8eb6291f68f1aed1152e9ddd56d2eb58 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Tue, 15 Jun 2021 20:09:17 +0530 Subject: [PATCH] feat: Add Public APIs for API Channel (#2375) --- .rubocop.yml | 1 + app/builders/contact_builder.rb | 7 +- .../api/v1/widget/messages_controller.rb | 1 + .../api/v1/inboxes/contacts_controller.rb | 48 +++++++++++++ .../v1/inboxes/conversations_controller.rb | 24 +++++++ .../api/v1/inboxes/messages_controller.rb | 68 +++++++++++++++++++ .../public/api/v1/inboxes_controller.rb | 23 +++++++ app/controllers/public_controller.rb | 3 + app/models/channel/api.rb | 21 ++++-- app/models/channel/web_widget.rb | 1 + .../v1/inboxes/contacts/create.json.jbuilder | 2 + .../v1/inboxes/contacts/show.json.jbuilder | 2 + .../v1/inboxes/contacts/update.json.jbuilder | 2 + .../conversations/create.json.jbuilder | 1 + .../inboxes/conversations/index.json.jbuilder | 3 + .../v1/inboxes/messages/create.json.jbuilder | 1 + .../v1/inboxes/messages/index.json.jbuilder | 3 + .../v1/inboxes/messages/update.json.jbuilder | 1 + .../api/v1/models/_contact.json.jbuilder | 4 ++ .../api/v1/models/_conversation.json.jbuilder | 10 +++ .../api/v1/models/_message.json.jbuilder | 9 +++ config/environments/development.rb | 3 + config/environments/production.rb | 9 ++- config/routes.rb | 18 +++++ .../20210602182058_add_hmac_to_api_channel.rb | 19 ++++++ db/schema.rb | 6 ++ .../api/v1/inbox/contacts_controller_spec.rb | 40 +++++++++++ .../v1/inbox/conversations_controller_spec.rb | 28 ++++++++ .../api/v1/inbox/messages_controller_spec.rb | 57 ++++++++++++++++ 29 files changed, 404 insertions(+), 11 deletions(-) create mode 100644 app/controllers/public/api/v1/inboxes/contacts_controller.rb create mode 100644 app/controllers/public/api/v1/inboxes/conversations_controller.rb create mode 100644 app/controllers/public/api/v1/inboxes/messages_controller.rb create mode 100644 app/controllers/public/api/v1/inboxes_controller.rb create mode 100644 app/controllers/public_controller.rb create mode 100644 app/views/public/api/v1/inboxes/contacts/create.json.jbuilder create mode 100644 app/views/public/api/v1/inboxes/contacts/show.json.jbuilder create mode 100644 app/views/public/api/v1/inboxes/contacts/update.json.jbuilder create mode 100644 app/views/public/api/v1/inboxes/conversations/create.json.jbuilder create mode 100644 app/views/public/api/v1/inboxes/conversations/index.json.jbuilder create mode 100644 app/views/public/api/v1/inboxes/messages/create.json.jbuilder create mode 100644 app/views/public/api/v1/inboxes/messages/index.json.jbuilder create mode 100644 app/views/public/api/v1/inboxes/messages/update.json.jbuilder create mode 100644 app/views/public/api/v1/models/_contact.json.jbuilder create mode 100644 app/views/public/api/v1/models/_conversation.json.jbuilder create mode 100644 app/views/public/api/v1/models/_message.json.jbuilder create mode 100644 db/migrate/20210602182058_add_hmac_to_api_channel.rb create mode 100644 spec/controllers/platform/api/v1/public/api/v1/inbox/contacts_controller_spec.rb create mode 100644 spec/controllers/platform/api/v1/public/api/v1/inbox/conversations_controller_spec.rb create mode 100644 spec/controllers/platform/api/v1/public/api/v1/inbox/messages_controller_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 77bd4ad18..4bec94c82 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -54,6 +54,7 @@ Rails/ApplicationController: - 'app/controllers/widget_tests_controller.rb' - 'app/controllers/widgets_controller.rb' - 'app/controllers/platform_controller.rb' + - 'app/controllers/public_controller.rb' Style/ClassAndModuleChildren: EnforcedStyle: compact Exclude: diff --git a/app/builders/contact_builder.rb b/app/builders/contact_builder.rb index f7fcdfb12..92c706a8a 100644 --- a/app/builders/contact_builder.rb +++ b/app/builders/contact_builder.rb @@ -1,5 +1,5 @@ class ContactBuilder - pattr_initialize [:source_id!, :inbox!, :contact_attributes!] + pattr_initialize [:source_id!, :inbox!, :contact_attributes!, :hmac_verified] def perform contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id) @@ -18,7 +18,8 @@ class ContactBuilder ::ContactInbox.create!( contact_id: contact.id, inbox_id: inbox.id, - source_id: source_id + source_id: source_id, + hmac_verified: hmac_verified || false ) end @@ -28,7 +29,7 @@ class ContactBuilder def create_contact account.contacts.create!( - name: contact_attributes[:name], + name: contact_attributes[:name] || ::Haikunator.haikunate(1000), phone_number: contact_attributes[:phone_number], email: contact_attributes[:email], identifier: contact_attributes[:identifier], diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb index fc55b0c1b..991b2f428 100644 --- a/app/controllers/api/v1/widget/messages_controller.rb +++ b/app/controllers/api/v1/widget/messages_controller.rb @@ -58,6 +58,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController end 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]) end diff --git a/app/controllers/public/api/v1/inboxes/contacts_controller.rb b/app/controllers/public/api/v1/inboxes/contacts_controller.rb new file mode 100644 index 000000000..6932386fd --- /dev/null +++ b/app/controllers/public/api/v1/inboxes/contacts_controller.rb @@ -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 diff --git a/app/controllers/public/api/v1/inboxes/conversations_controller.rb b/app/controllers/public/api/v1/inboxes/conversations_controller.rb new file mode 100644 index 000000000..a399bc749 --- /dev/null +++ b/app/controllers/public/api/v1/inboxes/conversations_controller.rb @@ -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 diff --git a/app/controllers/public/api/v1/inboxes/messages_controller.rb b/app/controllers/public/api/v1/inboxes/messages_controller.rb new file mode 100644 index 000000000..65a21183a --- /dev/null +++ b/app/controllers/public/api/v1/inboxes/messages_controller.rb @@ -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 diff --git a/app/controllers/public/api/v1/inboxes_controller.rb b/app/controllers/public/api/v1/inboxes_controller.rb new file mode 100644 index 000000000..a57e72e40 --- /dev/null +++ b/app/controllers/public/api/v1/inboxes_controller.rb @@ -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 diff --git a/app/controllers/public_controller.rb b/app/controllers/public_controller.rb new file mode 100644 index 000000000..615a5d610 --- /dev/null +++ b/app/controllers/public_controller.rb @@ -0,0 +1,3 @@ +class PublicController < ActionController::Base + skip_before_action :verify_authenticity_token +end diff --git a/app/models/channel/api.rb b/app/models/channel/api.rb index 672d8f383..7899da789 100644 --- a/app/models/channel/api.rb +++ b/app/models/channel/api.rb @@ -2,11 +2,19 @@ # # Table name: channel_api # -# id :bigint not null, primary key -# webhook_url :string -# created_at :datetime not null -# updated_at :datetime not null -# account_id :integer not null +# id :bigint not null, primary key +# hmac_mandatory :boolean default(FALSE) +# hmac_token :string +# identifier :string +# 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 @@ -15,6 +23,9 @@ class Channel::Api < ApplicationRecord validates :account_id, presence: true belongs_to :account + has_secure_token :identifier + has_secure_token :hmac_token + has_one :inbox, as: :channel, dependent: :destroy def name diff --git a/app/models/channel/web_widget.rb b/app/models/channel/web_widget.rb index 1f632cd51..793544531 100644 --- a/app/models/channel/web_widget.rb +++ b/app/models/channel/web_widget.rb @@ -4,6 +4,7 @@ # # id :integer not null, primary key # feature_flags :integer default(3), not null +# hmac_mandatory :boolean default(FALSE) # hmac_token :string # pre_chat_form_enabled :boolean default(FALSE) # pre_chat_form_options :jsonb diff --git a/app/views/public/api/v1/inboxes/contacts/create.json.jbuilder b/app/views/public/api/v1/inboxes/contacts/create.json.jbuilder new file mode 100644 index 000000000..256b588a5 --- /dev/null +++ b/app/views/public/api/v1/inboxes/contacts/create.json.jbuilder @@ -0,0 +1,2 @@ +json.source_id @contact_inbox.source_id +json.partial! 'public/api/v1/models/contact.json.jbuilder', resource: @contact_inbox.contact diff --git a/app/views/public/api/v1/inboxes/contacts/show.json.jbuilder b/app/views/public/api/v1/inboxes/contacts/show.json.jbuilder new file mode 100644 index 000000000..256b588a5 --- /dev/null +++ b/app/views/public/api/v1/inboxes/contacts/show.json.jbuilder @@ -0,0 +1,2 @@ +json.source_id @contact_inbox.source_id +json.partial! 'public/api/v1/models/contact.json.jbuilder', resource: @contact_inbox.contact diff --git a/app/views/public/api/v1/inboxes/contacts/update.json.jbuilder b/app/views/public/api/v1/inboxes/contacts/update.json.jbuilder new file mode 100644 index 000000000..256b588a5 --- /dev/null +++ b/app/views/public/api/v1/inboxes/contacts/update.json.jbuilder @@ -0,0 +1,2 @@ +json.source_id @contact_inbox.source_id +json.partial! 'public/api/v1/models/contact.json.jbuilder', resource: @contact_inbox.contact diff --git a/app/views/public/api/v1/inboxes/conversations/create.json.jbuilder b/app/views/public/api/v1/inboxes/conversations/create.json.jbuilder new file mode 100644 index 000000000..81d59cde5 --- /dev/null +++ b/app/views/public/api/v1/inboxes/conversations/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'public/api/v1/models/conversation.json.jbuilder', resource: @conversation diff --git a/app/views/public/api/v1/inboxes/conversations/index.json.jbuilder b/app/views/public/api/v1/inboxes/conversations/index.json.jbuilder new file mode 100644 index 000000000..97942b57b --- /dev/null +++ b/app/views/public/api/v1/inboxes/conversations/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @conversations do |conversation| + json.partial! 'public/api/v1/models/conversation.json.jbuilder', resource: conversation +end diff --git a/app/views/public/api/v1/inboxes/messages/create.json.jbuilder b/app/views/public/api/v1/inboxes/messages/create.json.jbuilder new file mode 100644 index 000000000..8b138e948 --- /dev/null +++ b/app/views/public/api/v1/inboxes/messages/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'public/api/v1/models/message.json.jbuilder', resource: @message diff --git a/app/views/public/api/v1/inboxes/messages/index.json.jbuilder b/app/views/public/api/v1/inboxes/messages/index.json.jbuilder new file mode 100644 index 000000000..124c79e2d --- /dev/null +++ b/app/views/public/api/v1/inboxes/messages/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @messages do |message| + json.partial! 'public/api/v1/models/message.json.jbuilder', resource: message +end diff --git a/app/views/public/api/v1/inboxes/messages/update.json.jbuilder b/app/views/public/api/v1/inboxes/messages/update.json.jbuilder new file mode 100644 index 000000000..8b138e948 --- /dev/null +++ b/app/views/public/api/v1/inboxes/messages/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'public/api/v1/models/message.json.jbuilder', resource: @message diff --git a/app/views/public/api/v1/models/_contact.json.jbuilder b/app/views/public/api/v1/models/_contact.json.jbuilder new file mode 100644 index 000000000..aa8abf2e2 --- /dev/null +++ b/app/views/public/api/v1/models/_contact.json.jbuilder @@ -0,0 +1,4 @@ +json.id resource.id +json.name resource.name +json.email resource.email +json.pubsub_token resource.pubsub_token diff --git a/app/views/public/api/v1/models/_conversation.json.jbuilder b/app/views/public/api/v1/models/_conversation.json.jbuilder new file mode 100644 index 000000000..7029238d9 --- /dev/null +++ b/app/views/public/api/v1/models/_conversation.json.jbuilder @@ -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 diff --git a/app/views/public/api/v1/models/_message.json.jbuilder b/app/views/public/api/v1/models/_message.json.jbuilder new file mode 100644 index 000000000..8aa42aba3 --- /dev/null +++ b/app/views/public/api/v1/models/_message.json.jbuilder @@ -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 diff --git a/config/environments/development.rb b/config/environments/development.rb index a03f8f2b4..eab9c411d 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -85,4 +85,7 @@ Rails.application.configure do resource '*', headers: :any, methods: :any, expose: ['access-token', 'client', 'uid', 'expiry'] end end + + # ref : https://medium.com/@emikaijuin/connecting-to-action-cable-without-rails-d39a8aaa52d5 + config.action_cable.disable_request_forgery_protection = true end diff --git a/config/environments/production.rb b/config/environments/production.rb index c812e7d10..c60fff3e6 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -42,9 +42,12 @@ Rails.application.configure do # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil # 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]+)?}] - end + + # to enable connecting to the API channel public APIs + 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. config.force_ssl = ActiveModel::Type::Boolean.new.cast(ENV.fetch('FORCE_SSL', false)) diff --git a/config/routes.rb b/config/routes.rb index bdeaf7c01..82876ceba 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -199,6 +199,24 @@ Rails.application.routes.draw do 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 resource :app, only: [:index] do diff --git a/db/migrate/20210602182058_add_hmac_to_api_channel.rb b/db/migrate/20210602182058_add_hmac_to_api_channel.rb new file mode 100644 index 000000000..f441c2829 --- /dev/null +++ b/db/migrate/20210602182058_add_hmac_to_api_channel.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index ec4849f74..4edb50157 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -143,6 +143,11 @@ ActiveRecord::Schema.define(version: 2021_06_09_133433) do t.string "webhook_url" t.datetime "created_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 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.boolean "pre_chat_form_enabled", default: false 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 ["website_token"], name: "index_channel_web_widgets_on_website_token", unique: true end diff --git a/spec/controllers/platform/api/v1/public/api/v1/inbox/contacts_controller_spec.rb b/spec/controllers/platform/api/v1/public/api/v1/inbox/contacts_controller_spec.rb new file mode 100644 index 000000000..284d21c83 --- /dev/null +++ b/spec/controllers/platform/api/v1/public/api/v1/inbox/contacts_controller_spec.rb @@ -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 diff --git a/spec/controllers/platform/api/v1/public/api/v1/inbox/conversations_controller_spec.rb b/spec/controllers/platform/api/v1/public/api/v1/inbox/conversations_controller_spec.rb new file mode 100644 index 000000000..1250ba526 --- /dev/null +++ b/spec/controllers/platform/api/v1/public/api/v1/inbox/conversations_controller_spec.rb @@ -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 diff --git a/spec/controllers/platform/api/v1/public/api/v1/inbox/messages_controller_spec.rb b/spec/controllers/platform/api/v1/public/api/v1/inbox/messages_controller_spec.rb new file mode 100644 index 000000000..7390d4dd9 --- /dev/null +++ b/spec/controllers/platform/api/v1/public/api/v1/inbox/messages_controller_spec.rb @@ -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