From 2542f26d96cffda95ca0f46c4d5fac321891048f Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 1 Nov 2024 15:35:02 +0200 Subject: [PATCH] adjust webhooks --- app/controllers/api/submissions_controller.rb | 14 ++++----- .../api/submitter_form_views_controller.rb | 6 ++-- app/controllers/api/submitters_controller.rb | 2 +- .../api/templates_clone_controller.rb | 6 ++-- app/controllers/api/templates_controller.rb | 6 ++-- app/controllers/start_form_controller.rb | 6 ++-- app/controllers/submissions_controller.rb | 12 ++++---- .../submit_form_decline_controller.rb | 6 ++-- app/controllers/templates_controller.rb | 12 ++++---- .../templates_uploads_controller.rb | 6 ++-- .../webhook_preferences_controller.rb | 22 ++++++++++++++ .../webhook_settings_controller.rb | 6 ++-- app/jobs/process_submitter_completion_job.rb | 10 +++---- ...send_form_completed_webhook_request_job.rb | 4 +-- .../send_form_declined_webhook_request_job.rb | 4 +-- .../send_form_started_webhook_request_job.rb | 4 +-- .../send_form_viewed_webhook_request_job.rb | 4 +-- ...submission_archived_webhook_request_job.rb | 4 +-- ...ubmission_completed_webhook_request_job.rb | 4 +-- ..._submission_created_webhook_request_job.rb | 4 +-- ...nd_template_created_webhook_request_job.rb | 4 +-- ...nd_template_updated_webhook_request_job.rb | 4 +-- app/models/webhook_url.rb | 10 ++----- app/views/webhook_settings/show.html.erb | 24 ++++++++------- config/routes.rb | 1 + ...241028162000_add_secret_to_webhook_urls.rb | 14 +++++++++ .../20241029192232_populate_webhook_urls.rb | 29 ++++++++++--------- db/schema.rb | 2 +- lib/submitters/submit_values.rb | 8 ++--- lib/webhook_urls.rb | 27 +++++++++++++++++ ...form_completed_webhook_request_job_spec.rb | 14 --------- ..._form_declined_webhook_request_job_spec.rb | 14 --------- ...d_form_started_webhook_request_job_spec.rb | 14 --------- ...nd_form_viewed_webhook_request_job_spec.rb | 14 --------- ...ssion_archived_webhook_request_job_spec.rb | 14 --------- ...sion_completed_webhook_request_job_spec.rb | 14 --------- ...ission_created_webhook_request_job_spec.rb | 14 --------- ...mplate_created_webhook_request_job_spec.rb | 14 --------- ...mplate_updated_webhook_request_job_spec.rb | 14 --------- spec/system/webhook_settings_spec.rb | 2 +- 40 files changed, 157 insertions(+), 236 deletions(-) create mode 100644 app/controllers/webhook_preferences_controller.rb create mode 100644 lib/webhook_urls.rb diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index c1f95d31..6337a123 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -67,10 +67,10 @@ module Api submissions = create_submissions(@template, params) - @template.account.webhook_urls.with_event('submission.created').each do |webhook_url| + WebhookUrls.for_account_id(@template.account_id, 'submission.created').each do |webhook_url| submissions.each do |submission| - SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id, - 'webhook_url_id' => webhook_url.id }) + SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id, + 'webhook_url_id' => webhook_url.id) end end @@ -78,7 +78,7 @@ module Api submissions.each do |submission| submission.submitters.each do |submitter| - ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submitter.id }) if submitter.completed_at? + ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id) if submitter.completed_at? end end @@ -96,9 +96,9 @@ module Api else @submission.update!(archived_at: Time.current) - @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 }) + WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url| + SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id, + 'webhook_url_id' => webhook_url.id) end end diff --git a/app/controllers/api/submitter_form_views_controller.rb b/app/controllers/api/submitter_form_views_controller.rb index e24df6b8..98d7f5b5 100644 --- a/app/controllers/api/submitter_form_views_controller.rb +++ b/app/controllers/api/submitter_form_views_controller.rb @@ -13,9 +13,9 @@ module Api SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request) - 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 }) + WebhookUrls.for_account_id(submitter.account_id, 'form.viewed').each do |webhook_url| + SendFormViewedWebhookRequestJob.perform_async('submitter_id' => submitter.id, + 'webhook_url_id' => webhook_url.id) end render json: {} diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index 210b783d..f85e2f60 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -68,7 +68,7 @@ module Api end if @submitter.completed_at? - ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => @submitter.id }) + ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id) elsif normalized_params[:send_email] || normalized_params[:send_sms] Submitters.send_signature_requests([@submitter]) end diff --git a/app/controllers/api/templates_clone_controller.rb b/app/controllers/api/templates_clone_controller.rb index 922739a4..f869fa28 100644 --- a/app/controllers/api/templates_clone_controller.rb +++ b/app/controllers/api/templates_clone_controller.rb @@ -25,9 +25,9 @@ module Api schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template) - 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 }) + WebhookUrls.for_account_id(cloned_template.account_id, '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) diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index c8c43e4a..80af5acf 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -65,9 +65,9 @@ module Api @template.update!(template_params) - @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 }) + WebhookUrls.for_account_id(@template.account_id, '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]) diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb index 63d0bb3b..209c81b4 100644 --- a/app/controllers/start_form_controller.rb +++ b/app/controllers/start_form_controller.rb @@ -38,9 +38,9 @@ class StartFormController < ApplicationController if @submitter.save if is_new_record - @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 }) + WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url| + SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id, + 'webhook_url_id' => webhook_url.id) end end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 2c47dca7..a7a7f216 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -66,9 +66,9 @@ class SubmissionsController < ApplicationController else @submission.update!(archived_at: Time.current) - @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 }) + WebhookUrls.for_account_id(@submission.account_id, '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') @@ -87,10 +87,10 @@ class SubmissionsController < ApplicationController end def enqueue_submission_created_webhooks(template, submissions) - template.account.webhook_urls.with_event('submission.created').each do |webhook_url| + WebhookUrls.for_account_id(template.account_id, 'submission.created').each do |webhook_url| submissions.each do |submission| - SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id, - 'webhook_url_id' => webhook_url.id }) + SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id, + 'webhook_url_id' => webhook_url.id) end end end diff --git a/app/controllers/submit_form_decline_controller.rb b/app/controllers/submit_form_decline_controller.rb index 1c4e0563..94099b0d 100644 --- a/app/controllers/submit_form_decline_controller.rb +++ b/app/controllers/submit_form_decline_controller.rb @@ -25,9 +25,9 @@ class SubmitFormDeclineController < ApplicationController SubmitterMailer.declined_email(submitter, user).deliver_later! end - 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 }) + WebhookUrls.for_account_id(submitter.account_id, '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) diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index ae63d3d0..5a578685 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -129,16 +129,16 @@ class TemplatesController < ApplicationController 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 }) + WebhookUrls.for_account_id(template.account_id, 'template.created').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 }) + WebhookUrls.for_account_id(template.account_id, 'template.updated').each do |webhook_url| + SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => template.id, + 'webhook_url_id' => webhook_url.id) end end diff --git a/app/controllers/templates_uploads_controller.rb b/app/controllers/templates_uploads_controller.rb index 069b316f..75b4e620 100644 --- a/app/controllers/templates_uploads_controller.rb +++ b/app/controllers/templates_uploads_controller.rb @@ -67,9 +67,9 @@ class TemplatesUploadsController < ApplicationController 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 }) + WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url| + SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id, + 'webhook_url_id' => webhook_url.id) end end end diff --git a/app/controllers/webhook_preferences_controller.rb b/app/controllers/webhook_preferences_controller.rb new file mode 100644 index 00000000..1070f7fe --- /dev/null +++ b/app/controllers/webhook_preferences_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class WebhookPreferencesController < ApplicationController + load_and_authorize_resource :webhook_url, parent: false + + def update + webhook_preferences_params[:events].each do |event, val| + @webhook_url.events.delete(event) if val == '0' + @webhook_url.events.push(event) if val == '1' && @webhook_url.events.exclude?(event) + end + + @webhook_url.save! + + head :ok + end + + private + + def webhook_preferences_params + params.require(:webhook_url).permit(events: {}) + end +end diff --git a/app/controllers/webhook_settings_controller.rb b/app/controllers/webhook_settings_controller.rb index 5482b478..2c6362af 100644 --- a/app/controllers/webhook_settings_controller.rb +++ b/app/controllers/webhook_settings_controller.rb @@ -17,8 +17,8 @@ class WebhookSettingsController < ApplicationController def update submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last - SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id, - 'webhook_url_id' => @webhook_url.id }) + SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id, + 'webhook_url_id' => @webhook_url.id) redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent')) end @@ -30,6 +30,6 @@ class WebhookSettingsController < ApplicationController end def webhook_params - params.require(:webhook_url).permit(:url, events: []) + params.require(:webhook_url).permit(:url) end end diff --git a/app/jobs/process_submitter_completion_job.rb b/app/jobs/process_submitter_completion_job.rb index 0b8c3e0b..c23c7f82 100644 --- a/app/jobs/process_submitter_completion_job.rb +++ b/app/jobs/process_submitter_completion_job.rb @@ -63,15 +63,15 @@ class ProcessSubmitterCompletionJob end def enqueue_completed_webhooks(submitter, is_all_completed: false) - submitter.account.webhook_urls.with_events(%w[form.completed submission.completed]).each do |webhook| + WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook| if webhook.events.include?('form.completed') - SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id, - 'webhook_url_id' => webhook.id }) + SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id, + 'webhook_url_id' => webhook.id) end if webhook.events.include?('submission.completed') && is_all_completed - SendSubmissionCompletedWebhookRequestJob.perform_async({ 'submission_id' => submitter.submission_id, - 'webhook_url_id' => webhook.id }) + SendSubmissionCompletedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id, + 'webhook_url_id' => webhook.id) end end end diff --git a/app/jobs/send_form_completed_webhook_request_job.rb b/app/jobs/send_form_completed_webhook_request_job.rb index 9d6c4c29..7e935e8d 100644 --- a/app/jobs/send_form_completed_webhook_request_job.rb +++ b/app/jobs/send_form_completed_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendFormCompletedWebhookRequestJob def perform(params = {}) submitter = Submitter.find(params['submitter_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = submitter.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('form.completed') Submissions::EnsureResultGenerated.call(submitter) diff --git a/app/jobs/send_form_declined_webhook_request_job.rb b/app/jobs/send_form_declined_webhook_request_job.rb index 58ee8640..2bf7b7f7 100644 --- a/app/jobs/send_form_declined_webhook_request_job.rb +++ b/app/jobs/send_form_declined_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendFormDeclinedWebhookRequestJob def perform(params = {}) submitter = Submitter.find(params['submitter_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = submitter.submission.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('form.declined') ActiveStorage::Current.url_options = Docuseal.default_url_options diff --git a/app/jobs/send_form_started_webhook_request_job.rb b/app/jobs/send_form_started_webhook_request_job.rb index 5a7a6d38..72dc40f7 100644 --- a/app/jobs/send_form_started_webhook_request_job.rb +++ b/app/jobs/send_form_started_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendFormStartedWebhookRequestJob def perform(params = {}) submitter = Submitter.find(params['submitter_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = submitter.submission.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('form.started') ActiveStorage::Current.url_options = Docuseal.default_url_options diff --git a/app/jobs/send_form_viewed_webhook_request_job.rb b/app/jobs/send_form_viewed_webhook_request_job.rb index a47c57b7..f71cf45f 100644 --- a/app/jobs/send_form_viewed_webhook_request_job.rb +++ b/app/jobs/send_form_viewed_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendFormViewedWebhookRequestJob def perform(params = {}) submitter = Submitter.find(params['submitter_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = submitter.submission.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('form.viewed') ActiveStorage::Current.url_options = Docuseal.default_url_options diff --git a/app/jobs/send_submission_archived_webhook_request_job.rb b/app/jobs/send_submission_archived_webhook_request_job.rb index a0141f50..bc6f4897 100644 --- a/app/jobs/send_submission_archived_webhook_request_job.rb +++ b/app/jobs/send_submission_archived_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendSubmissionArchivedWebhookRequestJob def perform(params = {}) submission = Submission.find(params['submission_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = submission.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.archived') resp = begin diff --git a/app/jobs/send_submission_completed_webhook_request_job.rb b/app/jobs/send_submission_completed_webhook_request_job.rb index 06ab92c9..0477a1b4 100644 --- a/app/jobs/send_submission_completed_webhook_request_job.rb +++ b/app/jobs/send_submission_completed_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendSubmissionCompletedWebhookRequestJob def perform(params = {}) submission = Submission.find(params['submission_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = submission.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.completed') resp = begin diff --git a/app/jobs/send_submission_created_webhook_request_job.rb b/app/jobs/send_submission_created_webhook_request_job.rb index e43ce493..31f83f23 100644 --- a/app/jobs/send_submission_created_webhook_request_job.rb +++ b/app/jobs/send_submission_created_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendSubmissionCreatedWebhookRequestJob def perform(params = {}) submission = Submission.find(params['submission_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = submission.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.created') resp = begin diff --git a/app/jobs/send_template_created_webhook_request_job.rb b/app/jobs/send_template_created_webhook_request_job.rb index 4a6cb896..49b344bc 100644 --- a/app/jobs/send_template_created_webhook_request_job.rb +++ b/app/jobs/send_template_created_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendTemplateCreatedWebhookRequestJob def perform(params = {}) template = Template.find(params['template_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = template.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('template.created') resp = begin diff --git a/app/jobs/send_template_updated_webhook_request_job.rb b/app/jobs/send_template_updated_webhook_request_job.rb index e36f53b3..0766a14d 100644 --- a/app/jobs/send_template_updated_webhook_request_job.rb +++ b/app/jobs/send_template_updated_webhook_request_job.rb @@ -11,12 +11,10 @@ class SendTemplateUpdatedWebhookRequestJob def perform(params = {}) template = Template.find(params['template_id']) + webhook_url = WebhookUrl.find(params['webhook_url_id']) attempt = params['attempt'].to_i - webhook_url = template.account.webhook_urls.find_by(id: params['webhook_url_id']) - - return unless webhook_url return if webhook_url.url.blank? || webhook_url.events.exclude?('template.updated') resp = begin diff --git a/app/models/webhook_url.rb b/app/models/webhook_url.rb index 18794c82..011bf5a1 100644 --- a/app/models/webhook_url.rb +++ b/app/models/webhook_url.rb @@ -6,7 +6,7 @@ # # id :bigint not null, primary key # events :text not null -# secret :text +# secret :text not null # sha1 :string not null # url :text not null # created_at :datetime not null @@ -37,17 +37,11 @@ class WebhookUrl < ApplicationRecord belongs_to :account attribute :events, :string, default: -> { %w[form.viewed form.started form.completed form.declined] } + attribute :secret, :string, default: -> { {} } 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 encrypts :url, :secret diff --git a/app/views/webhook_settings/show.html.erb b/app/views/webhook_settings/show.html.erb index eee89493..6c305d88 100644 --- a/app/views/webhook_settings/show.html.erb +++ b/app/views/webhook_settings/show.html.erb @@ -12,23 +12,27 @@
<%= 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' %> - <% if @webhook_url.persisted? %> + <% if @webhook_url.persisted? %> <%= @webhook_url.secret.present? ? t('edit_secret') : t('add_secret') %> <% end %>
+ <% end %> + <%= form_for @webhook_url, url: @webhook_url.url.present? ? webhook_preference_path(@webhook_url) : '', method: :put, html: { autocomplete: 'off' } do |f| %> <% WebhookUrl::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| %> -
- -
+ <% events.each do |event| %> + <%= f.fields_for :events do |ff| %> +
+ +
+ <% end %> <% end %>
<% end %> diff --git a/config/routes.rb b/config/routes.rb index c2b6e762..a2d5af4b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,6 +81,7 @@ Rails.application.routes.draw do resources :submitters_autocomplete, only: %i[index] resources :template_folders_autocomplete, only: %i[index] resources :webhook_secret, only: %i[show update] + resources :webhook_preferences, only: %i[update] resource :templates_upload, only: %i[create] authenticated do resource :templates_upload, only: %i[show], path: 'new' diff --git a/db/migrate/20241028162000_add_secret_to_webhook_urls.rb b/db/migrate/20241028162000_add_secret_to_webhook_urls.rb index 0befc646..5da2b6bd 100644 --- a/db/migrate/20241028162000_add_secret_to_webhook_urls.rb +++ b/db/migrate/20241028162000_add_secret_to_webhook_urls.rb @@ -1,7 +1,21 @@ # frozen_string_literal: true class AddSecretToWebhookUrls < ActiveRecord::Migration[7.2] + class MigrationWebhookUrl < ApplicationRecord + self.table_name = 'webhook_urls' + + serialize :secret, coder: JSON + + encrypts :url, :secret + end + def change add_column :webhook_urls, :secret, :text + + MigrationWebhookUrl.all.each do |url| + url.update_columns(secret: {}) + end + + change_column_null :webhook_urls, :secret, false end end diff --git a/db/migrate/20241029192232_populate_webhook_urls.rb b/db/migrate/20241029192232_populate_webhook_urls.rb index 36c393db..cb8b5546 100644 --- a/db/migrate/20241029192232_populate_webhook_urls.rb +++ b/db/migrate/20241029192232_populate_webhook_urls.rb @@ -3,42 +3,45 @@ class PopulateWebhookUrls < ActiveRecord::Migration[7.2] disable_ddl_transaction - class MigrationWebhookUrl < ApplicationRecord + class MigrationWebhookUrl < ActiveRecord::Base self.table_name = 'webhook_urls' serialize :events, coder: JSON serialize :secret, coder: JSON - encrypts :url, :secret - before_validation -> { self.sha1 = Digest::SHA1.hexdigest(url) } + encrypts :url, :secret end - class MigrationEncryptedConfig < ApplicationRecord + class MigrationEncryptedConfig < ActiveRecord::Base self.table_name = 'encrypted_configs' encrypts :value serialize :value, coder: JSON end - class MigrationAccountConfig < ApplicationRecord + class MigrationAccountConfig < ActiveRecord::Base 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 + MigrationEncryptedConfig.where(key: 'webhook_url').find_each do |config| + webhook_url = MigrationWebhookUrl.find_or_initialize_by(account_id: config.account_id, + sha1: Digest::SHA1.hexdigest(config.value)) + + webhook_url.secret = + MigrationEncryptedConfig.find_by(account_id: config.account_id, key: 'webhook_secret')&.value.to_h + + 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.url = config.value webhook_url.save! end diff --git a/db/schema.rb b/db/schema.rb index 6521993d..92e6a982 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -367,7 +367,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_29_192232) do t.string "sha1", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.text "secret" + t.text "secret", null: false t.index ["account_id"], name: "index_webhook_urls_on_account_id" t.index ["sha1"], name: "index_webhook_urls_on_sha1" end diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index be32fb91..40e96618 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -14,9 +14,9 @@ module Submitters unless submitter.submission_events.exists?(event_type: 'start_form') SubmissionEvents.create_with_tracking_data(submitter, 'start_form', request) - 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 }) + WebhookUrls.for_account_id(submitter.account_id, 'form.started').each do |webhook_url| + SendFormStartedWebhookRequestJob.perform_async('submitter_id' => submitter.id, + 'webhook_url_id' => webhook_url.id) end end @@ -24,7 +24,7 @@ module Submitters submitter.submission.save! - ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submitter.id }) if submitter.completed_at? + ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id) if submitter.completed_at? submitter end diff --git a/lib/webhook_urls.rb b/lib/webhook_urls.rb new file mode 100644 index 00000000..482e66a7 --- /dev/null +++ b/lib/webhook_urls.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module WebhookUrls + module_function + + def for_account_id(account_id, events) + events = Array.wrap(events) + + rel = WebhookUrl.where(account_id:) + + event_arel = events.map { |event| Arel::Table.new(:webhook_urls)[:events].matches("%\"#{event}\"%") }.reduce(:or) + + if Docuseal.multitenant? + rel.where(event_arel) + else + linked_account_rel = + AccountLinkedAccount.where(linked_account_id: account_id).where.not(account_type: :testing).select(:account_id) + + webhook_urls = rel.or(WebhookUrl.where(account_id: linked_account_rel).where(event_arel)) + + account_urls, linked_urls = webhook_urls.partition { |w| w.account_id == account_id } + + account_urls.select { |w| w.events.intersect?(events) }.presence || + (account_urls.present? ? WebhookUrl.none : linked_urls) + end + end +end diff --git a/spec/jobs/send_form_completed_webhook_request_job_spec.rb b/spec/jobs/send_form_completed_webhook_request_job_spec.rb index f9fd7cad..e4046cd9 100644 --- a/spec/jobs/send_form_completed_webhook_request_job_spec.rb +++ b/spec/jobs/send_form_completed_webhook_request_job_spec.rb @@ -56,14 +56,6 @@ RSpec.describe SendFormCompletedWebhookRequestJob do ).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']) @@ -72,12 +64,6 @@ RSpec.describe SendFormCompletedWebhookRequestJob do 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) diff --git a/spec/jobs/send_form_declined_webhook_request_job_spec.rb b/spec/jobs/send_form_declined_webhook_request_job_spec.rb index e6900fca..be2872b2 100644 --- a/spec/jobs/send_form_declined_webhook_request_job_spec.rb +++ b/spec/jobs/send_form_declined_webhook_request_job_spec.rb @@ -56,14 +56,6 @@ RSpec.describe SendFormDeclinedWebhookRequestJob do ).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']) @@ -72,12 +64,6 @@ RSpec.describe SendFormDeclinedWebhookRequestJob do 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) diff --git a/spec/jobs/send_form_started_webhook_request_job_spec.rb b/spec/jobs/send_form_started_webhook_request_job_spec.rb index 258e7274..3bff34eb 100644 --- a/spec/jobs/send_form_started_webhook_request_job_spec.rb +++ b/spec/jobs/send_form_started_webhook_request_job_spec.rb @@ -56,14 +56,6 @@ RSpec.describe SendFormStartedWebhookRequestJob do ).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']) @@ -72,12 +64,6 @@ RSpec.describe SendFormStartedWebhookRequestJob do 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) diff --git a/spec/jobs/send_form_viewed_webhook_request_job_spec.rb b/spec/jobs/send_form_viewed_webhook_request_job_spec.rb index cb23dcaa..66e1accb 100644 --- a/spec/jobs/send_form_viewed_webhook_request_job_spec.rb +++ b/spec/jobs/send_form_viewed_webhook_request_job_spec.rb @@ -56,14 +56,6 @@ RSpec.describe SendFormViewedWebhookRequestJob do ).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']) @@ -72,12 +64,6 @@ RSpec.describe SendFormViewedWebhookRequestJob do 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) diff --git a/spec/jobs/send_submission_archived_webhook_request_job_spec.rb b/spec/jobs/send_submission_archived_webhook_request_job_spec.rb index 3753ece8..87970214 100644 --- a/spec/jobs/send_submission_archived_webhook_request_job_spec.rb +++ b/spec/jobs/send_submission_archived_webhook_request_job_spec.rb @@ -53,14 +53,6 @@ RSpec.describe SendSubmissionArchivedWebhookRequestJob do ).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']) @@ -69,12 +61,6 @@ RSpec.describe SendSubmissionArchivedWebhookRequestJob do 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) diff --git a/spec/jobs/send_submission_completed_webhook_request_job_spec.rb b/spec/jobs/send_submission_completed_webhook_request_job_spec.rb index 999ffbb5..f4ba884c 100644 --- a/spec/jobs/send_submission_completed_webhook_request_job_spec.rb +++ b/spec/jobs/send_submission_completed_webhook_request_job_spec.rb @@ -53,14 +53,6 @@ RSpec.describe SendSubmissionCompletedWebhookRequestJob do ).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']) @@ -69,12 +61,6 @@ RSpec.describe SendSubmissionCompletedWebhookRequestJob do 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) diff --git a/spec/jobs/send_submission_created_webhook_request_job_spec.rb b/spec/jobs/send_submission_created_webhook_request_job_spec.rb index a3f804c1..ba635493 100644 --- a/spec/jobs/send_submission_created_webhook_request_job_spec.rb +++ b/spec/jobs/send_submission_created_webhook_request_job_spec.rb @@ -53,14 +53,6 @@ RSpec.describe SendSubmissionCreatedWebhookRequestJob do ).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']) @@ -69,12 +61,6 @@ RSpec.describe SendSubmissionCreatedWebhookRequestJob do 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) diff --git a/spec/jobs/send_template_created_webhook_request_job_spec.rb b/spec/jobs/send_template_created_webhook_request_job_spec.rb index 83a36c48..f6a09ba6 100644 --- a/spec/jobs/send_template_created_webhook_request_job_spec.rb +++ b/spec/jobs/send_template_created_webhook_request_job_spec.rb @@ -52,14 +52,6 @@ RSpec.describe SendTemplateCreatedWebhookRequestJob do ).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']) @@ -68,12 +60,6 @@ RSpec.describe SendTemplateCreatedWebhookRequestJob do 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) diff --git a/spec/jobs/send_template_updated_webhook_request_job_spec.rb b/spec/jobs/send_template_updated_webhook_request_job_spec.rb index bf463eca..58e46b63 100644 --- a/spec/jobs/send_template_updated_webhook_request_job_spec.rb +++ b/spec/jobs/send_template_updated_webhook_request_job_spec.rb @@ -52,14 +52,6 @@ RSpec.describe SendTemplateUpdatedWebhookRequestJob do ).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']) @@ -68,12 +60,6 @@ RSpec.describe SendTemplateUpdatedWebhookRequestJob do 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) diff --git a/spec/system/webhook_settings_spec.rb b/spec/system/webhook_settings_spec.rb index a6c1e214..a12b98f8 100644 --- a/spec/system/webhook_settings_spec.rb +++ b/spec/system/webhook_settings_spec.rb @@ -80,7 +80,7 @@ RSpec.describe 'Webhook Settings' do visit settings_webhooks_path - expect(webhook_url.secret).to be_nil + expect(webhook_url.secret).to eq({}) click_link 'Add Secret'