add webhook events

This commit is contained in:
Alex Turchyn
2025-07-04 21:11:08 +03:00
committed by Pete Matsyburka
parent 1b60b42428
commit 988a5361a6
54 changed files with 825 additions and 160 deletions
+15
View File
@@ -0,0 +1,15 @@
# frozen_string_literal: true
module HighlightCode
module_function
def call(code, lexer, theme: 'base16.light')
require 'rouge/themes/base16' unless Rouge::Theme.registry[theme]
formatter = Rouge::Formatters::HTMLInline.new(theme)
lexer = Rouge::Lexers.const_get(lexer.to_sym).new
formatted_code = formatter.format(lexer.lex(code))
formatted_code = formatted_code.gsub('background-color: #181818', '') if theme == 'base16.dark'
formatted_code
end
end
+66 -4
View File
@@ -5,12 +5,16 @@ module SendWebhookRequest
LOCALHOSTS = %w[0.0.0.0 127.0.0.1 localhost].freeze
MANUAL_ATTEMPT = 99_999
AUTOMATED_RETRY_RANGE = 1..MANUAL_ATTEMPT - 1
HttpsError = Class.new(StandardError)
LocalhostError = Class.new(StandardError)
module_function
def call(webhook_url, event_type:, data:)
# rubocop:disable Metrics/AbcSize
def call(webhook_url, event_uuid:, event_type:, record:, data:, attempt: 0)
uri = begin
URI(webhook_url.url)
rescue URI::Error
@@ -24,21 +28,79 @@ module SendWebhookRequest
raise LocalhostError, "Can't send to localhost." if uri.host.in?(LOCALHOSTS)
end
Faraday.post(uri) do |req|
webhook_event = create_webhook_event(webhook_url, event_uuid:, event_type:, record:)
return if AUTOMATED_RETRY_RANGE.cover?(attempt.to_i) && webhook_event&.status == 'success'
response = Faraday.post(uri) do |req|
req.headers['Content-Type'] = 'application/json'
req.headers['User-Agent'] = USER_AGENT
req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present?
req.body = {
event_type: event_type,
timestamp: Time.current,
timestamp: webhook_event&.created_at || Time.current,
data: data
}.to_json
req.options.read_timeout = 8
req.options.open_timeout = 8
end
rescue Faraday::Error
handle_response(webhook_event, response:, attempt:)
rescue Faraday::SSLError, Faraday::TimeoutError, Faraday::ConnectionFailed => e
handle_error(webhook_event, attempt:, error_message: e.class.name.split('::').last)
rescue Faraday::Error => e
handle_error(webhook_event, attempt:, error_message: e.message&.truncate(100))
end
# rubocop:enable Metrics/AbcSize
def create_webhook_event(webhook_url, event_uuid:, event_type:, record:)
return if event_uuid.blank?
WebhookEvent.create_with(
event_type:,
record:,
account_id: webhook_url.account_id,
status: 'pending'
).find_or_create_by!(webhook_url:, uuid: event_uuid)
end
def handle_response(webhook_event, response:, attempt:)
return response unless webhook_event
WebhookAttempt.create!(
webhook_event:,
response_body: response.body&.truncate(100),
response_status_code: response.status,
attempt:
)
webhook_event.update!(status: response.success? ? 'success' : 'error')
response
rescue StandardError
raise if Rails.env.local?
nil
end
def handle_error(webhook_event, error_message:, attempt:)
return unless webhook_event
WebhookAttempt.create!(
webhook_event:,
response_body: error_message,
response_status_code: 0,
attempt:
)
webhook_event.update!(status: 'error')
nil
rescue StandardError
raise if Rails.env.local?
nil
end
end
+1 -4
View File
@@ -16,10 +16,7 @@ module Submitters
unless submitter.submission_events.exists?(event_type: 'start_form')
SubmissionEvents.create_with_tracking_data(submitter, 'start_form', request)
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
WebhookUrls.enqueue_events(submitter, 'form.started')
end
update_submitter!(submitter, params, request, validate_required:)
+41
View File
@@ -1,6 +1,25 @@
# frozen_string_literal: true
module WebhookUrls
EVENT_TYPE_TO_JOB_CLASS = {
'form.started' => SendFormStartedWebhookRequestJob,
'form.completed' => SendFormCompletedWebhookRequestJob,
'form.declined' => SendFormDeclinedWebhookRequestJob,
'form.viewed' => SendFormViewedWebhookRequestJob,
'submission.created' => SendSubmissionCreatedWebhookRequestJob,
'submission.completed' => SendSubmissionCompletedWebhookRequestJob,
'submission.expired' => SendSubmissionExpiredWebhookRequestJob,
'submission.archived' => SendSubmissionArchivedWebhookRequestJob,
'template.created' => SendTemplateCreatedWebhookRequestJob,
'template.updated' => SendTemplateUpdatedWebhookRequestJob
}.freeze
EVENT_TYPE_ID_KEYS = {
'form' => 'submitter_id',
'submission' => 'submission_id',
'template' => 'template_id'
}.freeze
module_function
def for_account_id(account_id, events)
@@ -24,4 +43,26 @@ module WebhookUrls
(account_urls.present? ? WebhookUrl.none : linked_urls)
end
end
def enqueue_events(records, event_type)
args = []
id_key = EVENT_TYPE_ID_KEYS.fetch(event_type.split('.').first)
Array.wrap(records).group_by(&:account_id).each do |account_id, account_records|
webhook_urls = for_account_id(account_id, event_type)
account_records.each do |record|
event_uuid = SecureRandom.uuid
webhook_urls.each do |webhook_url|
next unless webhook_url.events.include?(event_type)
args << [{ id_key => record.id, 'webhook_url_id' => webhook_url.id, 'event_uuid' => event_uuid }]
end
end
end
Sidekiq::Client.push_bulk('class' => EVENT_TYPE_TO_JOB_CLASS[event_type], 'args' => args)
end
end