app/models/web_hook/event.rb
require 'securerandom'
class WebHook
class Event
class Error < ::StandardError; end
class MissingResourceError < Error; end
# This object represents one webhook event
# it checks if the webhook should be fired or not
# and encapsulates all the logic of getting right event, logging, etc.
attr_reader :id, :provider, :webhook, :resource, :options
def self.enqueue(provider, resource, options = {})
job = new(provider, resource, options)
job.valid? ? job.enqueue : job.ignore
end
def initialize(provider, resource, options = {})
@id = SecureRandom.uuid
@resource = resource or raise MissingResourceError
if (@provider = provider)
@webhook = provider.web_hook
end
@options = options.symbolize_keys
logger.info "New WebHook::Event(#{id}) #{event} for #{model}(##{resource.id})"
end
def event
@event ||= @options[:event] || guess_event
end
def model
resource.class.model_name
end
delegate :logger, to: :Rails
def trigger_transactional_callbacks?
true
end
def enqueue
if connection.try!(:transaction_open?) && !connection.current_transaction.state.finalized?
logger.info "Will enqueue WebHook::Event(#{id}) #{event} after commit for #{model}##{resource.id}"
connection.add_transaction_record(self)
else
committed!
end
end
def committed!(*)
enqueue_now
end
def before_committed!(*)
# noop
end
def rolledback!(*)
logger.info "Rolledback WebHook::Event(#{id}) #{event} for #{model}##{resource.id}"
end
def enqueue_now
logger.info "Pushed WebHook::Event(#{id}) #{event} for #{model}##{resource.id}"
args = { provider_id: provider.id, url: url, xml: to_xml, content_type: content_type }
WebHookWorker.perform_async(id, args.as_json)
end
def ignore
logger.info "Ignoring WebHook::Event(#{id}) #{event} for #{model}(#{resource.id})"
false
end
# this could be activemodel validation
def valid?
enabled? && push_event? && push_user? && provider.web_hooks_allowed?
end
# use i18n to figure this out
def resource_type
case model
when "Cinstance"
'application'
else
model.singular
end
end
def user
@user ||= options[:user]
end
def url
@url ||= WebHook.sanitized_url || webhook.url
end
def content_type
if webhook.push_application_content_type
'application/xml'
end
end
def to_xml(options = {})
builder = options[:builder] || ::ThreeScale::XML::Builder.new
builder.event do |xml|
xml.action event
xml.type_ resource_type
xml.object do |xml|
resource.to_xml(:builder => builder)
end
end
builder.to_xml
end
def enabled?
provider && webhook && webhook.enabled?(resource_type, event)
end
def push_user?
return true unless user
# return if providers accounts do not match
return unless resource.provider_account == self.provider
if user.account.buyer?
# check if user is buyer of provider of this webhook
user.account.provider_account == self.provider
elsif user.account.provider?
webhook.provider_actions
end
end
def push_event?
event.present?
end
protected
delegate :connection, to: 'ActiveRecord::Base'
# :reek:NilCheck
# That is fine as we want really to check for nil value in the changes
def guess_event
resource_changes = @resource.saved_changes
case
when @resource.destroyed?
'deleted'
when resource_changes.key?('created_at') && resource_changes['created_at'].first.nil?
'created'
when @resource.updated_at.present?
'updated'
end
end
end
end