Feature: Support account/inbox specific webhooks (#562)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Inbox::WebhooksController < Api::BaseController
|
||||
class Api::V1::Account::WebhooksController < Api::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_webhook, only: [:update, :destroy]
|
||||
|
||||
@@ -23,7 +23,7 @@ class Api::V1::Inbox::WebhooksController < Api::BaseController
|
||||
private
|
||||
|
||||
def webhook_params
|
||||
params.require(:webhook).permit(:account_id, :inbox_id, :urls).merge(urls: params[:urls])
|
||||
params.require(:webhook).permit(:account_id, :inbox_id, :url)
|
||||
end
|
||||
|
||||
def fetch_webhook
|
||||
@@ -3,13 +3,17 @@ class WebhookListener < BaseListener
|
||||
message = extract_message_and_account(event)[0]
|
||||
inbox = message.inbox
|
||||
|
||||
return unless message.reportable? && inbox.webhook.present?
|
||||
return unless message.reportable?
|
||||
|
||||
webhook = message.inbox.webhook
|
||||
payload = message.push_event_data.merge(event: __method__.to_s)
|
||||
payload = message.webhook_data.merge(event: __method__.to_s)
|
||||
# Account webhooks
|
||||
inbox.account.webhooks.account.each do |webhook|
|
||||
WebhookJob.perform_later(webhook.url, payload)
|
||||
end
|
||||
|
||||
webhook.urls.each do |url|
|
||||
WebhookJob.perform_later(url, payload)
|
||||
# Inbox webhooks
|
||||
inbox.webhooks.inbox.each do |webhook|
|
||||
WebhookJob.perform_later(webhook.url, payload)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -60,6 +60,13 @@ class Account < ApplicationRecord
|
||||
}
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
id: id,
|
||||
name: name
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_subscription
|
||||
|
||||
@@ -41,4 +41,11 @@ class Contact < ApplicationRecord
|
||||
pubsub_token: pubsub_token
|
||||
}
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
id: id,
|
||||
name: name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,6 +93,13 @@ class Conversation < ApplicationRecord
|
||||
Conversations::EventDataPresenter.new(self).lock_data
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
display_id: display_id,
|
||||
additional_attributes: additional_attributes
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch_events
|
||||
|
||||
@@ -33,7 +33,7 @@ class Inbox < ApplicationRecord
|
||||
has_many :members, through: :inbox_members, source: :user
|
||||
has_many :conversations, dependent: :destroy
|
||||
has_many :messages, through: :conversations
|
||||
has_one :webhook, dependent: :destroy
|
||||
has_many :webhooks, dependent: :destroy
|
||||
after_create :subscribe_webhook, if: :facebook?
|
||||
after_destroy :delete_round_robin_agents
|
||||
|
||||
@@ -60,6 +60,13 @@ class Inbox < ApplicationRecord
|
||||
account.users.find_by(id: user_id)
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
id: id,
|
||||
name: name
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_round_robin_agents
|
||||
|
||||
@@ -50,6 +50,7 @@ class Message < ApplicationRecord
|
||||
belongs_to :inbox
|
||||
belongs_to :conversation
|
||||
belongs_to :user, required: false
|
||||
belongs_to :contact, required: false
|
||||
|
||||
has_one :attachment, dependent: :destroy, autosave: true
|
||||
|
||||
@@ -78,6 +79,21 @@ class Message < ApplicationRecord
|
||||
incoming? || outgoing?
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
id: id,
|
||||
content: content,
|
||||
created_at: created_at,
|
||||
message_type: message_type,
|
||||
source_id: source_id,
|
||||
sender: user.try(:webhook_data),
|
||||
contact: contact.try(:webhook_data),
|
||||
inbox: inbox.webhook_data,
|
||||
conversation: conversation.webhook_data,
|
||||
account: account.webhook_data
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch_event
|
||||
|
||||
@@ -110,4 +110,12 @@ class User < ApplicationRecord
|
||||
avatar_url: avatar_url
|
||||
}
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
id: id,
|
||||
name: name,
|
||||
email: email
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
#
|
||||
# Table name: webhooks
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# urls :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer
|
||||
# inbox_id :integer
|
||||
# id :bigint not null, primary key
|
||||
# url :string
|
||||
# webhook_type :integer default("account")
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer
|
||||
# inbox_id :integer
|
||||
#
|
||||
|
||||
class Webhook < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :inbox
|
||||
belongs_to :inbox, optional: true
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
serialize :urls, Array
|
||||
|
||||
enum webhook_type: { account: 0, inbox: 1 }
|
||||
end
|
||||
|
||||
9
app/views/api/v1/account/webhooks/_webhook.json.jbuilder
Normal file
9
app/views/api/v1/account/webhooks/_webhook.json.jbuilder
Normal file
@@ -0,0 +1,9 @@
|
||||
json.id webhook.id
|
||||
json.url webhook.url
|
||||
json.account_id webhook.account_id
|
||||
if webhook.inbox
|
||||
json.inbox do
|
||||
json.id webhook.inbox.id
|
||||
json.name webhook.inbox.name
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
json.id webhook.id
|
||||
json.urls webhook.urls
|
||||
json.account_id webhook.account_id
|
||||
json.inbox do
|
||||
json.id webhook.inbox.id
|
||||
json.name webhook.inbox.name
|
||||
end
|
||||
@@ -38,7 +38,7 @@ Rails.application.routes.draw do
|
||||
resource :contact_merge, only: [:create]
|
||||
end
|
||||
|
||||
namespace :inbox do
|
||||
namespace :account do
|
||||
resources :webhooks, except: [:show]
|
||||
end
|
||||
|
||||
|
||||
5
db/migrate/20200225160650_rename_urls_to_url.rb
Normal file
5
db/migrate/20200225160650_rename_urls_to_url.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class RenameUrlsToUrl < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
rename_column :webhooks, :urls, :url
|
||||
end
|
||||
end
|
||||
5
db/migrate/20200225162150_add_type_to_webhook.rb
Normal file
5
db/migrate/20200225162150_add_type_to_webhook.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddTypeToWebhook < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :webhooks, :webhook_type, :integer, default: '0'
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2020_02_17_192734) do
|
||||
ActiveRecord::Schema.define(version: 2020_02_25_162150) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -262,9 +262,10 @@ ActiveRecord::Schema.define(version: 2020_02_17_192734) do
|
||||
create_table "webhooks", force: :cascade do |t|
|
||||
t.integer "account_id"
|
||||
t.integer "inbox_id"
|
||||
t.string "urls"
|
||||
t.string "url"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.integer "webhook_type", default: 0
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
|
||||
@@ -3,99 +3,91 @@ require 'rails_helper'
|
||||
RSpec.describe 'Webhooks API', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:webhook) { create(:webhook, account: account, inbox: inbox, urls: ['https://hello.com']) }
|
||||
let(:webhook) { create(:webhook, account: account, inbox: inbox, url: 'https://hello.com') }
|
||||
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
|
||||
describe 'GET /api/v1/inbox/webhooks' do
|
||||
describe 'GET /api/v1/account/webhooks' do
|
||||
context 'when it is an authenticated agent' do
|
||||
it 'returns unauthorized' do
|
||||
get '/api/v1/inbox/webhooks',
|
||||
get '/api/v1/account/webhooks',
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated admin user' do
|
||||
it 'gets all webhook' do
|
||||
get '/api/v1/inbox/webhooks',
|
||||
get '/api/v1/account/webhooks',
|
||||
headers: administrator.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(JSON.parse(response.body)['payload']['webhooks'].count).to eql account.webhooks.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/inbox/webhooks' do
|
||||
describe 'POST /api/v1/account/webhooks' do
|
||||
context 'when it is an authenticated agent' do
|
||||
it 'returns unauthorized' do
|
||||
post '/api/v1/inbox/webhooks',
|
||||
post '/api/v1/account/webhooks',
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated admin user' do
|
||||
it 'creates webhook' do
|
||||
post '/api/v1/inbox/webhooks',
|
||||
params: { account_id: account.id, inbox_id: inbox.id, urls: ['https://hello.com'] },
|
||||
post '/api/v1/account/webhooks',
|
||||
params: { account_id: account.id, inbox_id: inbox.id, url: 'https://hello.com' },
|
||||
headers: administrator.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
expect(JSON.parse(response.body)['payload']['webhook']['urls']).to eql ['https://hello.com']
|
||||
expect(JSON.parse(response.body)['payload']['webhook']['url']).to eql 'https://hello.com'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v1/inbox/webhooks/:id' do
|
||||
describe 'PUT /api/v1/account/webhooks/:id' do
|
||||
context 'when it is an authenticated agent' do
|
||||
it 'returns unauthorized' do
|
||||
put "/api/v1/inbox/webhooks/#{webhook.id}",
|
||||
put "/api/v1/account/webhooks/#{webhook.id}",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated admin user' do
|
||||
it 'updates webhook' do
|
||||
put "/api/v1/inbox/webhooks/#{webhook.id}",
|
||||
params: { urls: ['https://hello.com', 'https://world.com'] },
|
||||
put "/api/v1/account/webhooks/#{webhook.id}",
|
||||
params: { url: 'https://hello.com' },
|
||||
headers: administrator.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(JSON.parse(response.body)['payload']['webhook']['urls']).to eql ['https://hello.com', 'https://world.com']
|
||||
expect(JSON.parse(response.body)['payload']['webhook']['url']).to eql 'https://hello.com'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/v1/inbox/webhooks/:id' do
|
||||
describe 'DELETE /api/v1/account/webhooks/:id' do
|
||||
context 'when it is an authenticated agent' do
|
||||
it 'returns unauthorized' do
|
||||
delete "/api/v1/inbox/webhooks/#{webhook.id}",
|
||||
delete "/api/v1/account/webhooks/#{webhook.id}",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated admin user' do
|
||||
it 'deletes webhook' do
|
||||
delete "/api/v1/inbox/webhooks/#{webhook.id}",
|
||||
delete "/api/v1/account/webhooks/#{webhook.id}",
|
||||
headers: administrator.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(account.webhooks.count).to be 0
|
||||
end
|
||||
@@ -2,6 +2,6 @@ FactoryBot.define do
|
||||
factory :webhook do
|
||||
account_id { 1 }
|
||||
inbox_id { 1 }
|
||||
urls { ['MyString'] }
|
||||
url { 'MyString' }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ describe WebhookListener do
|
||||
context 'when webhook is configured' do
|
||||
it 'triggers webhook' do
|
||||
webhook = create(:webhook, inbox: inbox, account: account)
|
||||
expect(WebhookJob).to receive(:perform_later).with(webhook.urls[0], message.push_event_data.merge(event: 'message_created')).once
|
||||
expect(WebhookJob).to receive(:perform_later).with(webhook.url, message.webhook_data.merge(event: 'message_created')).once
|
||||
listener.message_created(event)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ RSpec.describe Inbox do
|
||||
it { is_expected.to have_many(:conversations).dependent(:destroy) }
|
||||
|
||||
it { is_expected.to have_many(:messages).through(:conversations) }
|
||||
it { is_expected.to have_one(:webhook) }
|
||||
it { is_expected.to have_many(:webhooks).dependent(:destroy) }
|
||||
end
|
||||
|
||||
describe '#add_member' do
|
||||
|
||||
@@ -3,11 +3,9 @@ require 'rails_helper'
|
||||
RSpec.describe Webhook, type: :model do
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:account_id) }
|
||||
it { is_expected.to validate_presence_of(:inbox_id) }
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:account) }
|
||||
it { is_expected.to belong_to(:inbox) }
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user