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 :check_authorization
|
||||||
before_action :fetch_webhook, only: [:update, :destroy]
|
before_action :fetch_webhook, only: [:update, :destroy]
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ class Api::V1::Inbox::WebhooksController < Api::BaseController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def webhook_params
|
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
|
end
|
||||||
|
|
||||||
def fetch_webhook
|
def fetch_webhook
|
||||||
@@ -3,13 +3,17 @@ class WebhookListener < BaseListener
|
|||||||
message = extract_message_and_account(event)[0]
|
message = extract_message_and_account(event)[0]
|
||||||
inbox = message.inbox
|
inbox = message.inbox
|
||||||
|
|
||||||
return unless message.reportable? && inbox.webhook.present?
|
return unless message.reportable?
|
||||||
|
|
||||||
webhook = message.inbox.webhook
|
payload = message.webhook_data.merge(event: __method__.to_s)
|
||||||
payload = message.push_event_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|
|
# Inbox webhooks
|
||||||
WebhookJob.perform_later(url, payload)
|
inbox.webhooks.inbox.each do |webhook|
|
||||||
|
WebhookJob.perform_later(webhook.url, payload)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ class Account < ApplicationRecord
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webhook_data
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_subscription
|
def create_subscription
|
||||||
|
|||||||
@@ -41,4 +41,11 @@ class Contact < ApplicationRecord
|
|||||||
pubsub_token: pubsub_token
|
pubsub_token: pubsub_token
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webhook_data
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ class Conversation < ApplicationRecord
|
|||||||
Conversations::EventDataPresenter.new(self).lock_data
|
Conversations::EventDataPresenter.new(self).lock_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webhook_data
|
||||||
|
{
|
||||||
|
display_id: display_id,
|
||||||
|
additional_attributes: additional_attributes
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def dispatch_events
|
def dispatch_events
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class Inbox < ApplicationRecord
|
|||||||
has_many :members, through: :inbox_members, source: :user
|
has_many :members, through: :inbox_members, source: :user
|
||||||
has_many :conversations, dependent: :destroy
|
has_many :conversations, dependent: :destroy
|
||||||
has_many :messages, through: :conversations
|
has_many :messages, through: :conversations
|
||||||
has_one :webhook, dependent: :destroy
|
has_many :webhooks, dependent: :destroy
|
||||||
after_create :subscribe_webhook, if: :facebook?
|
after_create :subscribe_webhook, if: :facebook?
|
||||||
after_destroy :delete_round_robin_agents
|
after_destroy :delete_round_robin_agents
|
||||||
|
|
||||||
@@ -60,6 +60,13 @@ class Inbox < ApplicationRecord
|
|||||||
account.users.find_by(id: user_id)
|
account.users.find_by(id: user_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webhook_data
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def delete_round_robin_agents
|
def delete_round_robin_agents
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class Message < ApplicationRecord
|
|||||||
belongs_to :inbox
|
belongs_to :inbox
|
||||||
belongs_to :conversation
|
belongs_to :conversation
|
||||||
belongs_to :user, required: false
|
belongs_to :user, required: false
|
||||||
|
belongs_to :contact, required: false
|
||||||
|
|
||||||
has_one :attachment, dependent: :destroy, autosave: true
|
has_one :attachment, dependent: :destroy, autosave: true
|
||||||
|
|
||||||
@@ -78,6 +79,21 @@ class Message < ApplicationRecord
|
|||||||
incoming? || outgoing?
|
incoming? || outgoing?
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def dispatch_event
|
def dispatch_event
|
||||||
|
|||||||
@@ -110,4 +110,12 @@ class User < ApplicationRecord
|
|||||||
avatar_url: avatar_url
|
avatar_url: avatar_url
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webhook_data
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
email: email
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,19 +2,20 @@
|
|||||||
#
|
#
|
||||||
# Table name: webhooks
|
# Table name: webhooks
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# urls :string
|
# url :string
|
||||||
# created_at :datetime not null
|
# webhook_type :integer default("account")
|
||||||
# updated_at :datetime not null
|
# created_at :datetime not null
|
||||||
# account_id :integer
|
# updated_at :datetime not null
|
||||||
# inbox_id :integer
|
# account_id :integer
|
||||||
|
# inbox_id :integer
|
||||||
#
|
#
|
||||||
|
|
||||||
class Webhook < ApplicationRecord
|
class Webhook < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :inbox
|
belongs_to :inbox, optional: true
|
||||||
|
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :inbox_id, presence: true
|
|
||||||
serialize :urls, Array
|
enum webhook_type: { account: 0, inbox: 1 }
|
||||||
end
|
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]
|
resource :contact_merge, only: [:create]
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :inbox do
|
namespace :account do
|
||||||
resources :webhooks, except: [:show]
|
resources :webhooks, except: [:show]
|
||||||
end
|
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.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
@@ -262,9 +262,10 @@ ActiveRecord::Schema.define(version: 2020_02_17_192734) do
|
|||||||
create_table "webhooks", force: :cascade do |t|
|
create_table "webhooks", force: :cascade do |t|
|
||||||
t.integer "account_id"
|
t.integer "account_id"
|
||||||
t.integer "inbox_id"
|
t.integer "inbox_id"
|
||||||
t.string "urls"
|
t.string "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.integer "webhook_type", default: 0
|
||||||
end
|
end
|
||||||
|
|
||||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
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
|
RSpec.describe 'Webhooks API', type: :request do
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:inbox) { create(:inbox, account: 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(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
let(:agent) { create(:user, account: account, role: :agent) }
|
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
|
context 'when it is an authenticated agent' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
get '/api/v1/inbox/webhooks',
|
get '/api/v1/account/webhooks',
|
||||||
headers: agent.create_new_auth_token,
|
headers: agent.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated admin user' do
|
context 'when it is an authenticated admin user' do
|
||||||
it 'gets all webhook' do
|
it 'gets all webhook' do
|
||||||
get '/api/v1/inbox/webhooks',
|
get '/api/v1/account/webhooks',
|
||||||
headers: administrator.create_new_auth_token,
|
headers: administrator.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
expect(JSON.parse(response.body)['payload']['webhooks'].count).to eql account.webhooks.count
|
expect(JSON.parse(response.body)['payload']['webhooks'].count).to eql account.webhooks.count
|
||||||
end
|
end
|
||||||
end
|
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
|
context 'when it is an authenticated agent' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
post '/api/v1/inbox/webhooks',
|
post '/api/v1/account/webhooks',
|
||||||
headers: agent.create_new_auth_token,
|
headers: agent.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated admin user' do
|
context 'when it is an authenticated admin user' do
|
||||||
it 'creates webhook' do
|
it 'creates webhook' do
|
||||||
post '/api/v1/inbox/webhooks',
|
post '/api/v1/account/webhooks',
|
||||||
params: { account_id: account.id, inbox_id: inbox.id, urls: ['https://hello.com'] },
|
params: { account_id: account.id, inbox_id: inbox.id, url: 'https://hello.com' },
|
||||||
headers: administrator.create_new_auth_token,
|
headers: administrator.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
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
|
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
|
context 'when it is an authenticated agent' do
|
||||||
it 'returns unauthorized' 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,
|
headers: agent.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated admin user' do
|
context 'when it is an authenticated admin user' do
|
||||||
it 'updates webhook' do
|
it 'updates webhook' do
|
||||||
put "/api/v1/inbox/webhooks/#{webhook.id}",
|
put "/api/v1/account/webhooks/#{webhook.id}",
|
||||||
params: { urls: ['https://hello.com', 'https://world.com'] },
|
params: { url: 'https://hello.com' },
|
||||||
headers: administrator.create_new_auth_token,
|
headers: administrator.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
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
|
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
|
context 'when it is an authenticated agent' do
|
||||||
it 'returns unauthorized' 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,
|
headers: agent.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when it is an authenticated admin user' do
|
context 'when it is an authenticated admin user' do
|
||||||
it 'deletes webhook' 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,
|
headers: administrator.create_new_auth_token,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
expect(account.webhooks.count).to be 0
|
expect(account.webhooks.count).to be 0
|
||||||
end
|
end
|
||||||
@@ -2,6 +2,6 @@ FactoryBot.define do
|
|||||||
factory :webhook do
|
factory :webhook do
|
||||||
account_id { 1 }
|
account_id { 1 }
|
||||||
inbox_id { 1 }
|
inbox_id { 1 }
|
||||||
urls { ['MyString'] }
|
url { 'MyString' }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe WebhookListener do
|
|||||||
context 'when webhook is configured' do
|
context 'when webhook is configured' do
|
||||||
it 'triggers webhook' do
|
it 'triggers webhook' do
|
||||||
webhook = create(:webhook, inbox: inbox, account: account)
|
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)
|
listener.message_created(event)
|
||||||
end
|
end
|
||||||
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(:conversations).dependent(:destroy) }
|
||||||
|
|
||||||
it { is_expected.to have_many(:messages).through(:conversations) }
|
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
|
end
|
||||||
|
|
||||||
describe '#add_member' do
|
describe '#add_member' do
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ require 'rails_helper'
|
|||||||
RSpec.describe Webhook, type: :model do
|
RSpec.describe Webhook, type: :model do
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
it { is_expected.to validate_presence_of(:account_id) }
|
it { is_expected.to validate_presence_of(:account_id) }
|
||||||
it { is_expected.to validate_presence_of(:inbox_id) }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'associations' do
|
describe 'associations' do
|
||||||
it { is_expected.to belong_to(:account) }
|
it { is_expected.to belong_to(:account) }
|
||||||
it { is_expected.to belong_to(:inbox) }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user