use webhook urls instead of encrypted configs
This commit is contained in:
committed by
Pete Matsyburka
parent
f669045362
commit
84edb6dbda
@@ -67,8 +67,11 @@ module Api
|
|||||||
|
|
||||||
submissions = create_submissions(@template, params)
|
submissions = create_submissions(@template, params)
|
||||||
|
|
||||||
submissions.each do |submission|
|
@template.account.webhook_urls.with_event('submission.created').each do |webhook_url|
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
@@ -93,7 +96,10 @@ module Api
|
|||||||
else
|
else
|
||||||
@submission.update!(archived_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
@submission.account.webhook_urls.with_event('submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async({ 'submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: @submission.as_json(only: %i[id archived_at])
|
render json: @submission.as_json(only: %i[id archived_at])
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ module Api
|
|||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
||||||
|
|
||||||
SendFormViewedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
submitter.account.webhook_urls.with_event('form.viewed').each do |webhook_url|
|
||||||
|
SendFormViewedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
|
||||||
render json: {}
|
render json: {}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ module Api
|
|||||||
|
|
||||||
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id)
|
cloned_template.account.webhook_urls.with_event('template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async({ 'template_id' => cloned_template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
|
||||||
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ module Api
|
|||||||
|
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
@template.account.webhook_urls.with_event('template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async({ 'template_id' => @template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
|
||||||
render json: @template.as_json(only: %i[id updated_at])
|
render json: @template.as_json(only: %i[id updated_at])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
if @submitter.save
|
if @submitter.save
|
||||||
if is_new_record
|
if is_new_record
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => @submitter.submission.id })
|
@submitter.account.webhook_urls.with_event('submission.created').each do |webhook_url|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => @submitter.submission_id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ class SubmissionsController < ApplicationController
|
|||||||
params: params.merge('send_completed_email' => true))
|
params: params.merge('send_completed_email' => true))
|
||||||
end
|
end
|
||||||
|
|
||||||
submissions.each do |submission|
|
enqueue_submission_created_webhooks(@template, submissions)
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
@@ -68,7 +66,10 @@ class SubmissionsController < ApplicationController
|
|||||||
else
|
else
|
||||||
@submission.update!(archived_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
@submission.account.webhook_urls.with_event('submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async({ 'submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
|
||||||
I18n.t('submission_has_been_archived')
|
I18n.t('submission_has_been_archived')
|
||||||
end
|
end
|
||||||
@@ -85,6 +86,15 @@ class SubmissionsController < ApplicationController
|
|||||||
template.save!
|
template.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_submission_created_webhooks(template, submissions)
|
||||||
|
template.account.webhook_urls.with_event('submission.created').each do |webhook_url|
|
||||||
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ class SubmitFormDeclineController < ApplicationController
|
|||||||
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
||||||
end
|
end
|
||||||
|
|
||||||
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id)
|
submitter.account.webhook_urls.with_event('form.declined').each do |webhook_url|
|
||||||
|
SendFormDeclinedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
|
||||||
redirect_to submit_form_path(submitter.slug)
|
redirect_to submit_form_path(submitter.slug)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class TemplatesController < ApplicationController
|
|||||||
if @template.save
|
if @template.save
|
||||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
maybe_redirect_to_template(@template)
|
maybe_redirect_to_template(@template)
|
||||||
else
|
else
|
||||||
@@ -77,7 +77,7 @@ class TemplatesController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_updated_webhooks(@template)
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
@@ -128,6 +128,20 @@ class TemplatesController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_template_created_webhooks(template)
|
||||||
|
template.account.webhook_urls.with_event('template.updated').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async({ 'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_template_updated_webhooks(template)
|
||||||
|
template.account.webhook_urls.with_event('template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async({ 'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def load_base_template
|
def load_base_template
|
||||||
return if params[:base_template_id].blank?
|
return if params[:base_template_id].blank?
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
@template.update!(schema:)
|
@template.update!(schema:)
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
redirect_to edit_template_path(@template)
|
redirect_to edit_template_path(@template)
|
||||||
rescue Templates::CreateAttachments::PdfEncrypted
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
@@ -65,4 +65,11 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
{ files: [file] }
|
{ files: [file] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_template_created_webhooks(template)
|
||||||
|
template.account.webhook_urls.with_event('template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async({ 'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ class TestingApiSettingsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
authorize!(:manage, current_user.access_token)
|
authorize!(:manage, current_user.access_token)
|
||||||
|
|
||||||
@webhook_config =
|
@webhook_url = current_account.webhook_urls.first_or_initialize
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
|
|
||||||
authorize!(:manage, @webhook_config)
|
authorize!(:manage, @webhook_url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class WebhookPreferencesController < ApplicationController
|
|
||||||
EVENTS = %w[
|
|
||||||
form.viewed
|
|
||||||
form.started
|
|
||||||
form.completed
|
|
||||||
form.declined
|
|
||||||
template.created
|
|
||||||
template.updated
|
|
||||||
submission.created
|
|
||||||
submission.archived
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
before_action :load_account_config
|
|
||||||
authorize_resource :account_config, parent: false
|
|
||||||
|
|
||||||
def create
|
|
||||||
@account_config.value[account_config_params[:event]] = account_config_params[:value] == '1'
|
|
||||||
|
|
||||||
@account_config.save!
|
|
||||||
|
|
||||||
head :ok
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def load_account_config
|
|
||||||
@account_config =
|
|
||||||
current_account.account_configs.find_or_initialize_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
@account_config.value ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_config_params
|
|
||||||
params.permit(:event, :value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookSecretController < ApplicationController
|
class WebhookSecretController < ApplicationController
|
||||||
before_action :load_encrypted_config
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
authorize_resource :encrypted_config, parent: false
|
|
||||||
|
|
||||||
def index; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def update
|
||||||
@encrypted_config.assign_attributes(value: {
|
@webhook_url.update!(secret: {
|
||||||
encrypted_config_params[:key] => encrypted_config_params[:value]
|
webhook_secret_params[:key] => webhook_secret_params[:value]
|
||||||
}.compact_blank)
|
}.compact_blank)
|
||||||
|
|
||||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_secret_has_been_saved'))
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_secret_has_been_saved'))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_encrypted_config
|
def webhook_secret_params
|
||||||
@encrypted_config =
|
params.require(:webhook_url).permit(secret: %i[key value]).fetch(:secret, {})
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_SECRET_KEY)
|
|
||||||
end
|
|
||||||
|
|
||||||
def encrypted_config_params
|
|
||||||
params.require(:encrypted_config).permit(value: %i[key value]).fetch(:value, {})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookSettingsController < ApplicationController
|
class WebhookSettingsController < ApplicationController
|
||||||
before_action :load_encrypted_config
|
before_action :load_webhook_url
|
||||||
authorize_resource :encrypted_config, parent: false
|
authorize_resource :webhook_url, parent: false
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@encrypted_config.assign_attributes(encrypted_config_params)
|
@webhook_url.assign_attributes(webhook_params)
|
||||||
|
|
||||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
@webhook_url.url.present? ? @webhook_url.save! : @webhook_url.delete
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved'))
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved'))
|
||||||
end
|
end
|
||||||
@@ -18,19 +18,18 @@ class WebhookSettingsController < ApplicationController
|
|||||||
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
||||||
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
||||||
'encrypted_config_id' => @encrypted_config.id })
|
'webhook_url_id' => @webhook_url.id })
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_encrypted_config
|
def load_webhook_url
|
||||||
@encrypted_config =
|
@webhook_url = current_account.webhook_urls.first_or_initialize
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def encrypted_config_params
|
def webhook_params
|
||||||
params.require(:encrypted_config).permit(:value)
|
params.require(:webhook_url).permit(:url, events: [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -63,24 +63,7 @@ class ProcessSubmitterCompletionJob
|
|||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
||||||
webhook_config = Accounts.load_webhook_config(submitter.account)
|
submitter.account.webhook_urls.with_events(%w[form.completed submission.completed]).each do |webhook|
|
||||||
|
|
||||||
if webhook_config
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
|
||||||
'encrypted_config_id' => webhook_config.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
webhook_urls = submitter.account.webhook_urls
|
|
||||||
|
|
||||||
webhook_urls = webhook_urls.where(
|
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"form.completed"%')
|
|
||||||
).or(
|
|
||||||
webhook_urls.where(
|
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"submission.completed"%')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
webhook_urls.each do |webhook|
|
|
||||||
if webhook.events.include?('form.completed')
|
if webhook.events.include?('form.completed')
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
||||||
'webhook_url_id' => webhook.id })
|
'webhook_url_id' => webhook.id })
|
||||||
|
|||||||
@@ -14,22 +14,23 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
url, secret = load_url_and_secret(submitter, params)
|
webhook_url = submitter.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
|
|
||||||
return if url.blank?
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.completed')
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(submitter)
|
Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.completed',
|
event_type: 'form.completed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**secret.to_h,
|
**webhook_url.secret.to_h,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -45,27 +46,4 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_url_and_secret(submitter, params)
|
|
||||||
if params['encrypted_config_id']
|
|
||||||
config = EncryptedConfig.find(params['encrypted_config_id'])
|
|
||||||
|
|
||||||
url = config.value
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.completed'] == false
|
|
||||||
|
|
||||||
secret = EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h
|
|
||||||
|
|
||||||
[url, secret]
|
|
||||||
elsif params['webhook_url_id']
|
|
||||||
webhook_url = submitter.account.webhook_urls.find(params['webhook_url_id'])
|
|
||||||
|
|
||||||
webhook_url.url if webhook_url.events.include?('form.completed')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,26 +13,22 @@ class SendFormDeclinedWebhookRequestJob
|
|||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
webhook_url = submitter.submission.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.declined')
|
||||||
return if preferences['form.declined'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.declined',
|
event_type: 'form.declined',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +39,7 @@ class SendFormDeclinedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,26 +13,22 @@ class SendFormStartedWebhookRequestJob
|
|||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
webhook_url = submitter.submission.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.started')
|
||||||
return if preferences['form.started'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.started',
|
event_type: 'form.started',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +39,7 @@ class SendFormStartedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,26 +13,22 @@ class SendFormViewedWebhookRequestJob
|
|||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
webhook_url = submitter.submission.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.viewed')
|
||||||
return if preferences['form.viewed'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.viewed',
|
event_type: 'form.viewed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +39,7 @@ class SendFormViewedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,24 +14,19 @@ class SendSubmissionArchivedWebhookRequestJob
|
|||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(submission.account)
|
webhook_url = submission.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.archived')
|
||||||
preferences = Accounts.load_webhook_preferences(submission.account)
|
|
||||||
|
|
||||||
return if preferences['submission.archived'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.archived',
|
event_type: 'submission.archived',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: submission.as_json(only: %i[id archived_at])
|
data: submission.as_json(only: %i[id archived_at])
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +37,7 @@ class SendSubmissionArchivedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submission_id' => submission.id,
|
'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,21 +14,19 @@ class SendSubmissionCompletedWebhookRequestJob
|
|||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
webhook_url = submission.account.webhook_urls.find(params['webhook_url_id'])
|
webhook_url = submission.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
|
|
||||||
url = webhook_url.url if webhook_url.events.include?('submission.completed')
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.completed')
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.completed',
|
event_type: 'submission.completed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submissions::SerializeForApi.call(submission)
|
data: Submissions::SerializeForApi.call(submission)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: submission.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
|
|||||||
@@ -14,24 +14,19 @@ class SendSubmissionCreatedWebhookRequestJob
|
|||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(submission.account)
|
webhook_url = submission.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.created')
|
||||||
preferences = Accounts.load_webhook_preferences(submission.account)
|
|
||||||
|
|
||||||
return if preferences['submission.created'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.created',
|
event_type: 'submission.created',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submissions::SerializeForApi.call(submission)
|
data: Submissions::SerializeForApi.call(submission)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +37,7 @@ class SendSubmissionCreatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submission_id' => submission.id,
|
'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,24 +14,19 @@ class SendTemplateCreatedWebhookRequestJob
|
|||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(template.account)
|
webhook_url = template.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.created')
|
||||||
preferences = Accounts.load_webhook_preferences(template.account)
|
|
||||||
|
|
||||||
return if preferences['template.created'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'template.created',
|
event_type: 'template.created',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Templates::SerializeForApi.call(template)
|
data: Templates::SerializeForApi.call(template)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +37,7 @@ class SendTemplateCreatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'template_id' => template.id,
|
'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,24 +14,19 @@ class SendTemplateUpdatedWebhookRequestJob
|
|||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(template.account)
|
webhook_url = template.account.webhook_urls.find_by(id: params['webhook_url_id'])
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return unless webhook_url
|
||||||
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.updated')
|
||||||
preferences = Accounts.load_webhook_preferences(template.account)
|
|
||||||
|
|
||||||
return if preferences['template.updated'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'template.updated',
|
event_type: 'template.updated',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Templates::SerializeForApi.call(template)
|
data: Templates::SerializeForApi.call(template)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +37,7 @@ class SendTemplateUpdatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'template_id' => template.id,
|
'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class AccountConfig < ApplicationRecord
|
|||||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||||
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
|
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
|
||||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||||
WEBHOOK_PREFERENCES_KEY = 'webhook_preferences'
|
|
||||||
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
||||||
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
||||||
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ class EncryptedConfig < ApplicationRecord
|
|||||||
EMAIL_SMTP_KEY = 'action_mailer_smtp',
|
EMAIL_SMTP_KEY = 'action_mailer_smtp',
|
||||||
ESIGN_CERTS_KEY = 'esign_certs',
|
ESIGN_CERTS_KEY = 'esign_certs',
|
||||||
TIMESTAMP_SERVER_URL_KEY = 'timestamp_server_url',
|
TIMESTAMP_SERVER_URL_KEY = 'timestamp_server_url',
|
||||||
APP_URL_KEY = 'app_url',
|
APP_URL_KEY = 'app_url'
|
||||||
WEBHOOK_URL_KEY = 'webhook_url',
|
|
||||||
WEBHOOK_SECRET_KEY = 'webhook_secret'
|
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# events :text not null
|
# events :text not null
|
||||||
|
# secret :text
|
||||||
# sha1 :string not null
|
# sha1 :string not null
|
||||||
# url :text not null
|
# url :text not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
@@ -22,15 +23,34 @@
|
|||||||
# fk_rails_... (account_id => accounts.id)
|
# fk_rails_... (account_id => accounts.id)
|
||||||
#
|
#
|
||||||
class WebhookUrl < ApplicationRecord
|
class WebhookUrl < ApplicationRecord
|
||||||
|
EVENTS = %w[
|
||||||
|
form.viewed
|
||||||
|
form.started
|
||||||
|
form.completed
|
||||||
|
form.declined
|
||||||
|
template.created
|
||||||
|
template.updated
|
||||||
|
submission.created
|
||||||
|
submission.archived
|
||||||
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
attribute :events, :string, default: -> { [] }
|
attribute :events, :string, default: -> { %w[form.viewed form.started form.completed form.declined] }
|
||||||
|
|
||||||
serialize :events, coder: JSON
|
serialize :events, coder: JSON
|
||||||
|
serialize :secret, coder: JSON
|
||||||
|
|
||||||
|
scope :with_event, ->(event) { with_events([event]) }
|
||||||
|
scope :with_events, lambda { |events|
|
||||||
|
where(events.map do |event|
|
||||||
|
Arel::Table.new(:webhook_urls)[:events].matches("%\"#{event}\"%")
|
||||||
|
end.reduce(:or))
|
||||||
|
}
|
||||||
|
|
||||||
before_validation :set_sha1
|
before_validation :set_sha1
|
||||||
|
|
||||||
encrypts :url
|
encrypts :url, :secret
|
||||||
|
|
||||||
def set_sha1
|
def set_sha1
|
||||||
self.sha1 = Digest::SHA1.hexdigest(url)
|
self.sha1 = Digest::SHA1.hexdigest(url)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::WEBHOOK_URL_KEY, account: current_account)) %>
|
<% if can?(:read, WebhookUrl) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= form_for @webhook_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for @webhook_url, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<%= f.label :value, 'Webhook URL', class: 'text-sm font-semibold' %>
|
<%= f.label :url, 'Webhook URL', class: 'text-sm font-semibold' %>
|
||||||
<div class="space-y-2 md:flex-nowrap mt-2">
|
<div class="space-y-2 md:flex-nowrap mt-2">
|
||||||
<%= f.url_field :value, class: 'base-input w-full', placeholder: 'https://example.com/hook' %>
|
<%= f.url_field :url, class: 'base-input w-full', placeholder: 'https://example.com/hook' %>
|
||||||
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full' %>
|
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<%= render 'shared/turbo_modal', title: 'Webhook Secret' do %>
|
<%= render 'shared/turbo_modal', title: t('webhook_secret') do %>
|
||||||
<%= form_for @encrypted_config, url: webhook_secret_index_path, method: :post, html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for @webhook_url, url: webhook_secret_path, method: :patch, html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= f.fields_for :value, Struct.new(:key, :value).new(*@encrypted_config.value.to_a.first) do |ff| %>
|
<%= f.fields_for :secret, Struct.new(:key, :value).new(*@webhook_url.secret.to_a.first) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :key, class: 'label' %>
|
<%= ff.label :key, t('key'), class: 'label' %>
|
||||||
<%= ff.text_field :key, class: 'base-input', placeholder: 'X-Example-Header' %>
|
<%= ff.text_field :key, class: 'base-input', placeholder: 'X-Example-Header' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :value, class: 'label' %>
|
<%= ff.label :value, t('value'), class: 'label' %>
|
||||||
<%= ff.text_field :value, class: 'base-input' %>
|
<%= ff.text_field :value, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -7,34 +7,31 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card bg-base-200">
|
<div class="card bg-base-200">
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6">
|
||||||
<%= form_for @encrypted_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %>
|
<%= form_for @webhook_url, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %>
|
||||||
<%= f.label :value, 'Webhook URL', class: 'text-sm font-semibold' %>
|
<%= f.label :url, 'Webhook URL', class: 'text-sm font-semibold' %>
|
||||||
<div class="flex flex-row flex-wrap space-y-2 md:space-y-0 md:flex-nowrap md:space-x-2 mt-2">
|
<div class="flex flex-row flex-wrap space-y-2 md:space-y-0 md:flex-nowrap md:space-x-2 mt-2">
|
||||||
<%= f.url_field :value, class: 'input font-mono input-bordered w-full', placeholder: 'https://example.com/hook' %>
|
<%= f.url_field :url, class: 'input font-mono input-bordered w-full', placeholder: 'https://example.com/hook' %>
|
||||||
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full md:w-32' %>
|
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full md:w-32' %>
|
||||||
<a href="<%= webhook_secret_index_path %>" data-turbo-frame="modal" class="white-button w-full md:w-auto">
|
<% if @webhook_url.persisted? %>
|
||||||
<%= t('add_secret') %>
|
<a href="<%= webhook_secret_path(@webhook_url) %>" data-turbo-frame="modal" class="white-button w-full md:w-auto">
|
||||||
</a>
|
<%= @webhook_url.secret.present? ? t('edit_secret') : t('add_secret') %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% WebhookUrl::EVENTS.group_by { |e| e.include?('form') }.each do |_, events| %>
|
||||||
<% preference = current_account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)&.value || {} %>
|
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 mt-4 gap-y-2">
|
||||||
<% WebhookPreferencesController::EVENTS.group_by { |e| e.include?('form') }.each do |_, events| %>
|
<%= f.collection_check_boxes(:events, events, :to_s, :to_s, include_hidden: false) do |b| %>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 mt-2 gap-y-2">
|
|
||||||
<% events.each do |event| %>
|
|
||||||
<%= form_for '', url: webhook_preferences_path, method: :post do |f| %>
|
|
||||||
<%= f.hidden_field :event, value: event %>
|
|
||||||
<% uuid = SecureRandom.uuid %>
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<label for="<%= uuid %>" class="flex items-center space-x-2">
|
<label class="flex items-center space-x-2">
|
||||||
<%= f.check_box :value, class: 'base-checkbox', checked: preference[event] || (!preference.key?(event) && event.starts_with?('form.')), onchange: 'this.form.requestSubmit()', id: uuid %>
|
<%= b.check_box class: 'base-checkbox', checked: @webhook_url.events.include?(b.value), onchange: 'this.form.requestSubmit()', disabled: @webhook_url.url.blank? %>
|
||||||
<span>
|
<span>
|
||||||
<%= event %>
|
<%= b.label %>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,7 +44,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('submission_example_payload') %>
|
<%= t('submission_example_payload') %>
|
||||||
</span>
|
</span>
|
||||||
<% if @encrypted_config.value.present? %>
|
<% if @webhook_url.url.present? && @webhook_url.events.include?('form.completed') %>
|
||||||
<%= button_to button_title(title: 'Test Webhook', disabled_with: t('sending'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), settings_webhooks_path, class: 'btn btn-neutral btn-outline btn-sm', method: :put %>
|
<%= button_to button_title(title: 'Test Webhook', disabled_with: t('sending'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), settings_webhooks_path, class: 'btn btn-neutral btn-outline btn-sm', method: :put %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+24
-1
@@ -474,6 +474,7 @@ en: &en
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Back
|
back: Back
|
||||||
add_secret: Add Secret
|
add_secret: Add Secret
|
||||||
|
edit_secret: Edit Secret
|
||||||
submission_example_payload: Submission example payload
|
submission_example_payload: Submission example payload
|
||||||
there_are_no_signatures: There are no signatures
|
there_are_no_signatures: There are no signatures
|
||||||
signed_with_trusted_certificate: Signed with trusted certificate
|
signed_with_trusted_certificate: Signed with trusted certificate
|
||||||
@@ -623,7 +624,9 @@ en: &en
|
|||||||
archived_users: Archived Users
|
archived_users: Archived Users
|
||||||
embedding_users: Embedding Users
|
embedding_users: Embedding Users
|
||||||
view_embedding_users: View Embedding Users
|
view_embedding_users: View Embedding Users
|
||||||
view_users: View Users
|
key: Key
|
||||||
|
value: Value
|
||||||
|
webhook_secret: Webhook Secret
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
||||||
@@ -1116,6 +1119,7 @@ es: &es
|
|||||||
logo: Logotipo
|
logo: Logotipo
|
||||||
back: Atrás
|
back: Atrás
|
||||||
add_secret: Agregar secreto
|
add_secret: Agregar secreto
|
||||||
|
edit_secret: Editar secreto
|
||||||
submission_example_payload: Ejemplo de payload de envío
|
submission_example_payload: Ejemplo de payload de envío
|
||||||
there_are_no_signatures: No hay firmas
|
there_are_no_signatures: No hay firmas
|
||||||
signed_with_trusted_certificate: Firmado con certificado de confianza
|
signed_with_trusted_certificate: Firmado con certificado de confianza
|
||||||
@@ -1265,6 +1269,9 @@ es: &es
|
|||||||
archived_users: Usuarios Archivados
|
archived_users: Usuarios Archivados
|
||||||
embedding_users: Usuarios Integrados
|
embedding_users: Usuarios Integrados
|
||||||
view_embedding_users: Ver Usuarios Integrado
|
view_embedding_users: Ver Usuarios Integrado
|
||||||
|
key: Clave
|
||||||
|
value: Valor
|
||||||
|
webhook_secret: Secreto del Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
||||||
@@ -1757,6 +1764,7 @@ it: &it
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Indietro
|
back: Indietro
|
||||||
add_secret: Aggiungi segreto
|
add_secret: Aggiungi segreto
|
||||||
|
edit_secret: Modifica segreto
|
||||||
submission_example_payload: Esempio di payload di invio
|
submission_example_payload: Esempio di payload di invio
|
||||||
there_are_no_signatures: Non ci sono firme
|
there_are_no_signatures: Non ci sono firme
|
||||||
signed_with_trusted_certificate: Firmato con certificato affidabile
|
signed_with_trusted_certificate: Firmato con certificato affidabile
|
||||||
@@ -1906,6 +1914,9 @@ it: &it
|
|||||||
archived_users: Utenti Archiviati
|
archived_users: Utenti Archiviati
|
||||||
embedding_users: Utenti Incorporati
|
embedding_users: Utenti Incorporati
|
||||||
view_embedding_users: Visualizza Utenti Incorporati
|
view_embedding_users: Visualizza Utenti Incorporati
|
||||||
|
key: Chiave
|
||||||
|
value: Valore
|
||||||
|
webhook_secret: Segreto del Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
||||||
@@ -2399,6 +2410,7 @@ fr: &fr
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Retour
|
back: Retour
|
||||||
add_secret: Ajouter un secret
|
add_secret: Ajouter un secret
|
||||||
|
edit_secret: Modifier le Secret
|
||||||
submission_example_payload: Exemple de payload de soumission
|
submission_example_payload: Exemple de payload de soumission
|
||||||
there_are_no_signatures: "Il n'y a pas de signatures"
|
there_are_no_signatures: "Il n'y a pas de signatures"
|
||||||
signed_with_trusted_certificate: Signé avec un certificat de confiance
|
signed_with_trusted_certificate: Signé avec un certificat de confiance
|
||||||
@@ -2548,6 +2560,9 @@ fr: &fr
|
|||||||
archived_users: Utilisateurs Archivés
|
archived_users: Utilisateurs Archivés
|
||||||
embedding_users: Utilisateurs Intégrés
|
embedding_users: Utilisateurs Intégrés
|
||||||
view_embedding_users: Voir les Utilisateurs Intégrés
|
view_embedding_users: Voir les Utilisateurs Intégrés
|
||||||
|
key: Clé
|
||||||
|
value: Valeur
|
||||||
|
webhook_secret: Secret du Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
||||||
@@ -3040,6 +3055,7 @@ pt: &pt
|
|||||||
logo: Logotipo
|
logo: Logotipo
|
||||||
back: Voltar
|
back: Voltar
|
||||||
add_secret: Adicionar segredo
|
add_secret: Adicionar segredo
|
||||||
|
edit_secret: Editar Segredo
|
||||||
submission_example_payload: Exemplo de payload de submissão
|
submission_example_payload: Exemplo de payload de submissão
|
||||||
there_are_no_signatures: Não há assinaturas
|
there_are_no_signatures: Não há assinaturas
|
||||||
signed_with_trusted_certificate: Assinado com certificado confiável
|
signed_with_trusted_certificate: Assinado com certificado confiável
|
||||||
@@ -3189,6 +3205,9 @@ pt: &pt
|
|||||||
archived_users: Usuários Arquivados
|
archived_users: Usuários Arquivados
|
||||||
embedding_users: Usuários Incorporados
|
embedding_users: Usuários Incorporados
|
||||||
view_embedding_users: Ver Usuários Incorporados
|
view_embedding_users: Ver Usuários Incorporados
|
||||||
|
key: Chave
|
||||||
|
value: Valor
|
||||||
|
webhook_secret: Segredo do Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
||||||
@@ -3681,6 +3700,7 @@ de: &de
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Zurück
|
back: Zurück
|
||||||
add_secret: Geheimnis hinzufügen
|
add_secret: Geheimnis hinzufügen
|
||||||
|
edit_secret: Geheimnis bearbeiten
|
||||||
submission_example_payload: Beispiel-Payload für Einreichung
|
submission_example_payload: Beispiel-Payload für Einreichung
|
||||||
there_are_no_signatures: Es gibt keine Unterschriften
|
there_are_no_signatures: Es gibt keine Unterschriften
|
||||||
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
|
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
|
||||||
@@ -3830,6 +3850,9 @@ de: &de
|
|||||||
archived_users: Archivierte Benutzer
|
archived_users: Archivierte Benutzer
|
||||||
embedding_users: Einbettende Benutzer
|
embedding_users: Einbettende Benutzer
|
||||||
view_embedding_users: Einbettende Benutzer anzeigen
|
view_embedding_users: Einbettende Benutzer anzeigen
|
||||||
|
key: Schlüssel
|
||||||
|
value: Wert
|
||||||
|
webhook_secret: Webhook-Geheimnis
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
||||||
|
|||||||
+1
-2
@@ -80,8 +80,7 @@ Rails.application.routes.draw do
|
|||||||
resources :testing_api_settings, only: %i[index]
|
resources :testing_api_settings, only: %i[index]
|
||||||
resources :submitters_autocomplete, only: %i[index]
|
resources :submitters_autocomplete, only: %i[index]
|
||||||
resources :template_folders_autocomplete, only: %i[index]
|
resources :template_folders_autocomplete, only: %i[index]
|
||||||
resources :webhook_preferences, only: %i[create]
|
resources :webhook_secret, only: %i[show update]
|
||||||
resources :webhook_secret, only: %i[index create]
|
|
||||||
resource :templates_upload, only: %i[create]
|
resource :templates_upload, only: %i[create]
|
||||||
authenticated do
|
authenticated do
|
||||||
resource :templates_upload, only: %i[show], path: 'new'
|
resource :templates_upload, only: %i[show], path: 'new'
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddSecretToWebhookUrls < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
add_column :webhook_urls, :secret, :text
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PopulateWebhookUrls < ActiveRecord::Migration[7.2]
|
||||||
|
disable_ddl_transaction
|
||||||
|
|
||||||
|
class MigrationWebhookUrl < ApplicationRecord
|
||||||
|
self.table_name = 'webhook_urls'
|
||||||
|
|
||||||
|
serialize :events, coder: JSON
|
||||||
|
serialize :secret, coder: JSON
|
||||||
|
encrypts :url, :secret
|
||||||
|
|
||||||
|
before_validation -> { self.sha1 = Digest::SHA1.hexdigest(url) }
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationEncryptedConfig < ApplicationRecord
|
||||||
|
self.table_name = 'encrypted_configs'
|
||||||
|
|
||||||
|
encrypts :value
|
||||||
|
serialize :value, coder: JSON
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationAccountConfig < ApplicationRecord
|
||||||
|
self.table_name = 'account_configs'
|
||||||
|
|
||||||
|
serialize :value, coder: JSON
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
MigrationEncryptedConfig.joins('INNER JOIN accounts a ON a.id = encrypted_configs.account_id')
|
||||||
|
.where(key: 'webhook_url')
|
||||||
|
.find_each do |config|
|
||||||
|
webhook_url = MigrationWebhookUrl.find_or_initialize_by(account_id: config.account_id, url: config.value)
|
||||||
|
webhook_url.secret = MigrationEncryptedConfig.find_by(account_id: config.account_id, key: 'webhook_secret')&.value
|
||||||
|
|
||||||
|
preferences = MigrationAccountConfig.find_by(account_id: config.account_id,
|
||||||
|
key: 'webhook_preferences')&.value.to_h
|
||||||
|
events = %w[form.viewed form.started form.completed form.declined].reject { |event| preferences[event] == false }
|
||||||
|
events += preferences.compact_blank.keys
|
||||||
|
|
||||||
|
webhook_url.events = events.uniq
|
||||||
|
|
||||||
|
webhook_url.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
+2
-1
@@ -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[7.2].define(version: 2024_10_26_161207) do
|
ActiveRecord::Schema[7.2].define(version: 2024_10_29_192232) 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"
|
||||||
|
|
||||||
@@ -367,6 +367,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_26_161207) do
|
|||||||
t.string "sha1", null: false
|
t.string "sha1", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.text "secret"
|
||||||
t.index ["account_id"], name: "index_webhook_urls_on_account_id"
|
t.index ["account_id"], name: "index_webhook_urls_on_account_id"
|
||||||
t.index ["sha1"], name: "index_webhook_urls_on_sha1"
|
t.index ["sha1"], name: "index_webhook_urls_on_sha1"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ class Ability
|
|||||||
can :manage, UserConfig, user_id: user.id
|
can :manage, UserConfig, user_id: user.id
|
||||||
can :manage, Account, id: user.account_id
|
can :manage, Account, id: user.account_id
|
||||||
can :manage, AccessToken, user_id: user.id
|
can :manage, AccessToken, user_id: user.id
|
||||||
|
can :manage, WebhookUrl, account_id: user.account_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -78,30 +78,6 @@ module Accounts
|
|||||||
new_template
|
new_template
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_webhook_url(account)
|
|
||||||
load_webhook_config(account)&.value.presence
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_webhook_config(account)
|
|
||||||
configs = account.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
|
|
||||||
if !configs && !Docuseal.multitenant? && !account.testing?
|
|
||||||
configs = Account.order(:id).first.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
end
|
|
||||||
|
|
||||||
configs
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_webhook_preferences(account)
|
|
||||||
configs = account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
|
|
||||||
unless Docuseal.multitenant?
|
|
||||||
configs ||= Account.order(:id).first.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
end
|
|
||||||
|
|
||||||
configs&.value.presence || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_signing_pkcs(account)
|
def load_signing_pkcs(account)
|
||||||
cert_data =
|
cert_data =
|
||||||
if Docuseal.multitenant?
|
if Docuseal.multitenant?
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ module Submitters
|
|||||||
unless submitter.submission_events.exists?(event_type: 'start_form')
|
unless submitter.submission_events.exists?(event_type: 'start_form')
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'start_form', request)
|
SubmissionEvents.create_with_tracking_data(submitter, 'start_form', request)
|
||||||
|
|
||||||
SendFormStartedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
submitter.account.webhook_urls.with_event('form.started').each do |webhook_url|
|
||||||
|
SendFormStartedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
update_submitter!(submitter, params, request)
|
update_submitter!(submitter, params, request)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :webhook_url do
|
||||||
|
account
|
||||||
|
url { Faker::Internet.url }
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormCompletedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.completed']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the submitter doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.declined'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormDeclinedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.declined']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.declined',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.declined',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the submitter doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.completed'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormStartedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.started']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.started',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.started',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the submitter doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.declined'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormViewedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.viewed']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.viewed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.viewed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the submitter doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.started'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendSubmissionArchivedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['submission.archived']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.archived',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => submission.reload.as_json(only: %i[id archived_at])
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.archived',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => submission.reload.as_json(only: %i[id archived_at])
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the submission doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['submission.created'])
|
||||||
|
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submission_id']).to eq(submission.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id,
|
||||||
|
'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendSubmissionCompletedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, :with_submitters, template:, created_by_user: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['submission.completed']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the submission doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['submission.archived'])
|
||||||
|
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submission_id']).to eq(submission.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id,
|
||||||
|
'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendSubmissionCreatedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, :with_submitters, template:, created_by_user: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['submission.created']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the submission doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['submission.completed'])
|
||||||
|
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submission_id']).to eq(submission.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id,
|
||||||
|
'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendTemplateCreatedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['template.created']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the template doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['template.updated'])
|
||||||
|
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['template_id']).to eq(template.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendTemplateUpdatedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['template.updated']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.updated',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.updated',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.co Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the template doesn't exist" do
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => 100_500, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to raise_error ActiveRecord::RecordNotFound
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['template.created'])
|
||||||
|
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the webhook doesn't exist" do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => 100_500)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['template_id']).to eq(template.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -67,3 +67,19 @@ RSpec.configure do |config|
|
|||||||
Sidekiq::Testing.inline! if example.metadata[:sidekiq] == :inline
|
Sidekiq::Testing.inline! if example.metadata[:sidekiq] == :inline
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def replace_timestamps(data, replace = /.*/)
|
||||||
|
timestamp_fields = %w[created_at updated_at completed_at sent_at opened_at timestamp]
|
||||||
|
|
||||||
|
data.each do |key, value|
|
||||||
|
if timestamp_fields.include?(key) && (value.is_a?(String) || value.is_a?(Time))
|
||||||
|
data[key] = replace
|
||||||
|
elsif value.is_a?(Hash)
|
||||||
|
replace_timestamps(value)
|
||||||
|
elsif value.is_a?(Array)
|
||||||
|
value.each { |item| replace_timestamps(item) if item.is_a?(Hash) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|||||||
@@ -8,24 +8,110 @@ RSpec.describe 'Webhook Settings' do
|
|||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
visit settings_webhooks_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows webhook settings page' do
|
it 'shows webhook settings page' do
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
expect(page).to have_content('Webhooks')
|
expect(page).to have_content('Webhooks')
|
||||||
expect(page).to have_field('Webhook URL')
|
expect(page).to have_field('Webhook URL')
|
||||||
expect(page).to have_button('Save')
|
expect(page).to have_button('Save')
|
||||||
|
|
||||||
|
WebhookUrl::EVENTS.each do |event|
|
||||||
|
expect(page).to have_field(event, type: 'checkbox', disabled: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates the webhook URL' do
|
it 'creates the webhook' do
|
||||||
fill_in 'Webhook URL', with: 'https://example.com'
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
fill_in 'Webhook URL', with: 'https://example.com/webhook'
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
click_button 'Save'
|
click_button 'Save'
|
||||||
end.to change(EncryptedConfig, :count).by(1)
|
end.to change(WebhookUrl, :count).by(1)
|
||||||
|
|
||||||
encrypted_config = EncryptedConfig.find_by(account:, key: EncryptedConfig::WEBHOOK_URL_KEY)
|
webhook_url = account.webhook_urls.first
|
||||||
|
|
||||||
expect(encrypted_config.value).to eq('https://example.com')
|
expect(webhook_url.url).to eq('https://example.com/webhook')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the webhook' do
|
||||||
|
webhook_url = create(:webhook_url, account:, url: 'https://example.com/webhook')
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
fill_in 'Webhook URL', with: 'https://example.org/webhook'
|
||||||
|
click_button 'Save'
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.url).to eq('https://example.org/webhook')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes the webhook' do
|
||||||
|
create(:webhook_url, account:)
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
fill_in 'Webhook URL', with: ''
|
||||||
|
|
||||||
|
expect do
|
||||||
|
click_button 'Save'
|
||||||
|
end.to change(WebhookUrl, :count).by(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the webhook events' do
|
||||||
|
webhook_url = create(:webhook_url, account:)
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
expect(webhook_url.events).not_to include('submission.created')
|
||||||
|
|
||||||
|
check('submission.created')
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.events).to include('submission.created')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds a secret to the webhook' do
|
||||||
|
webhook_url = create(:webhook_url, account:)
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
expect(webhook_url.secret).to be_nil
|
||||||
|
|
||||||
|
click_link 'Add Secret'
|
||||||
|
|
||||||
|
within '#modal' do
|
||||||
|
fill_in 'Key', with: 'X-Signature'
|
||||||
|
fill_in 'Value', with: 'secret-value'
|
||||||
|
|
||||||
|
click_button 'Submit'
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.secret).to eq({ 'X-Signature' => 'secret-value' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes a secret from the webhook' do
|
||||||
|
webhook_url = create(:webhook_url, account:, secret: { 'X-Signature' => 'secret-value' })
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
click_link 'Edit Secret'
|
||||||
|
|
||||||
|
within '#modal' do
|
||||||
|
fill_in 'Key', with: ''
|
||||||
|
fill_in 'Value', with: ''
|
||||||
|
|
||||||
|
click_button 'Submit'
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.secret).to eq({})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user