Feature: Support account/inbox specific webhooks (#562)

This commit is contained in:
Subin T P
2020-02-26 09:44:24 +05:30
committed by GitHub
parent 4d5e7b4adf
commit 7479b5db43
24 changed files with 117 additions and 57 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -60,6 +60,13 @@ class Account < ApplicationRecord
}
end
def webhook_data
{
id: id,
name: name
}
end
private
def create_subscription

View File

@@ -41,4 +41,11 @@ class Contact < ApplicationRecord
pubsub_token: pubsub_token
}
end
def webhook_data
{
id: id,
name: name
}
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -110,4 +110,12 @@ class User < ApplicationRecord
avatar_url: avatar_url
}
end
def webhook_data
{
id: id,
name: name,
email: email
}
end
end

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
class RenameUrlsToUrl < ActiveRecord::Migration[6.0]
def change
rename_column :webhooks, :urls, :url
end
end

View File

@@ -0,0 +1,5 @@
class AddTypeToWebhook < ActiveRecord::Migration[6.0]
def change
add_column :webhooks, :webhook_type, :integer, default: '0'
end
end

View File

@@ -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"

View File

@@ -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

View File

@@ -2,6 +2,6 @@ FactoryBot.define do
factory :webhook do
account_id { 1 }
inbox_id { 1 }
urls { ['MyString'] }
url { 'MyString' }
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -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