Initial commit
This commit is contained in:
62
lib/peatio/amqp/config.rb
Normal file
62
lib/peatio/amqp/config.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module AMQP
|
||||
class Config
|
||||
class <<self
|
||||
def data
|
||||
@data ||= Hashie::Mash.new(
|
||||
YAML.safe_load(
|
||||
ERB.new(File.read(Rails.root.join('config', 'amqp.yml'))).result
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def connect
|
||||
data[:connect]
|
||||
end
|
||||
|
||||
def binding_exchange_id(id)
|
||||
data[:binding][id][:exchange]
|
||||
end
|
||||
|
||||
def binding_exchange(id)
|
||||
eid = binding_exchange_id(id)
|
||||
eid && exchange(eid)
|
||||
end
|
||||
|
||||
def binding_queue(id)
|
||||
queue data[:binding][id][:queue]
|
||||
end
|
||||
|
||||
def binding_worker(id)
|
||||
::Workers::AMQP.const_get(id.to_s.camelize).new
|
||||
end
|
||||
|
||||
def routing_key(id)
|
||||
binding_queue(id).first
|
||||
end
|
||||
|
||||
def topics(id)
|
||||
data[:binding][id][:topics].split(',')
|
||||
end
|
||||
|
||||
def channel(id)
|
||||
(data[:channel] && data[:channel][id]) || {}
|
||||
end
|
||||
|
||||
def queue(id)
|
||||
name = data[:queue][id][:name]
|
||||
settings = { durable: data[:queue][id][:durable] }
|
||||
[name, settings]
|
||||
end
|
||||
|
||||
def exchange(id)
|
||||
type = data[:exchange][id][:type]
|
||||
name = data[:exchange][id][:name]
|
||||
[type, name]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
219
lib/peatio/amqp/event_api.rb
Normal file
219
lib/peatio/amqp/event_api.rb
Normal file
@@ -0,0 +1,219 @@
|
||||
module EventAPI
|
||||
|
||||
MAPPING = {
|
||||
'withdraw' => 'system.user.withdraw.confirm.code',
|
||||
'withdraw-coin' => 'system.user.withdraw.confirm.code',
|
||||
'withdraw-fiat' => 'system.user.withdraw.confirm.code'
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
def notify(event_name, event_payload)
|
||||
event_name = MAPPING.dig(event_name).present? ? MAPPING.dig(event_name) : event_name
|
||||
raise('Dena mailer event name is unknown') unless event_name.present?
|
||||
|
||||
arguments = [event_name, event_payload]
|
||||
middlewares.each do |middleware|
|
||||
returned_value = middleware.call(*arguments)
|
||||
case returned_value
|
||||
when Array then arguments = returned_value
|
||||
else return returned_value
|
||||
end
|
||||
rescue StandardError => e
|
||||
report_exception(e)
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def middlewares=(list)
|
||||
@middlewares = list
|
||||
end
|
||||
|
||||
def middlewares
|
||||
@middlewares ||= []
|
||||
end
|
||||
end
|
||||
|
||||
module ActiveRecord
|
||||
class Mediator
|
||||
attr_reader :record
|
||||
|
||||
def initialize(record)
|
||||
@record = record
|
||||
end
|
||||
|
||||
def notify(partial_event_name, event_payload)
|
||||
tokens = ['model']
|
||||
tokens << record.class.event_api_settings.fetch(:prefix) { record.class.name.underscore.gsub(/\//, '_') }
|
||||
tokens << partial_event_name.to_s
|
||||
full_event_name = tokens.join('.')
|
||||
EventAPI.notify(full_event_name, event_payload)
|
||||
end
|
||||
|
||||
def notify_record_created
|
||||
notify(:created, record: record.as_json_for_event_api.compact)
|
||||
end
|
||||
|
||||
def notify_record_updated
|
||||
return if record.previous_changes.blank?
|
||||
|
||||
current_record = record
|
||||
previous_record = record.dup
|
||||
record.previous_changes.each { |attribute, values| previous_record.send("#{attribute}=", values.first) }
|
||||
|
||||
# Guarantee timestamps.
|
||||
previous_record.created_at ||= current_record.created_at
|
||||
previous_record.updated_at ||= current_record.created_at
|
||||
|
||||
before = previous_record.as_json_for_event_api.compact
|
||||
after = current_record.as_json_for_event_api.compact
|
||||
|
||||
notify :updated, \
|
||||
record: after,
|
||||
changes: before.delete_if { |attribute, value| after[attribute] == value }
|
||||
end
|
||||
end
|
||||
|
||||
module Extension
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# We add «after_commit» callbacks immediately after inclusion.
|
||||
%i[create update].each do |event|
|
||||
after_commit on: event, prepend: true do
|
||||
if self.class.event_api_settings[:on]&.include?(event)
|
||||
event_api.public_send("notify_record_#{event}d")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def acts_as_eventable(settings = {})
|
||||
settings[:on] = %i[create update] unless settings.key?(:on)
|
||||
@event_api_settings = event_api_settings.merge(settings)
|
||||
end
|
||||
|
||||
def event_api_settings
|
||||
@event_api_settings || superclass.instance_variable_get(:@event_api_settings) || {}
|
||||
end
|
||||
end
|
||||
|
||||
def event_api
|
||||
@event_api ||= Mediator.new(self)
|
||||
end
|
||||
|
||||
def as_json_for_event_api
|
||||
as_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# To continue processing by further middlewares return array with event name and payload.
|
||||
# To stop processing event return any value which isn't an array.
|
||||
module Middlewares
|
||||
|
||||
class << self
|
||||
def application_name
|
||||
Rails.application.class.name.split('::').first.underscore
|
||||
end
|
||||
|
||||
def application_version
|
||||
"#{application_name.camelize}::VERSION".constantize
|
||||
end
|
||||
end
|
||||
|
||||
class IncludeEventMetadata
|
||||
def call(event_name, event_payload)
|
||||
event_payload[:name] = event_name
|
||||
[event_name, event_payload]
|
||||
end
|
||||
end
|
||||
|
||||
class GenerateJWT
|
||||
def call(event_name, event_payload)
|
||||
jwt_payload = {
|
||||
iss: Middlewares.application_name,
|
||||
jti: SecureRandom.uuid,
|
||||
iat: Time.now.to_i,
|
||||
exp: Time.now.to_i + 60,
|
||||
event: event_payload
|
||||
}
|
||||
private_key = OpenSSL::PKey.read(Base64.urlsafe_decode64(ENV.fetch('EVENT_API_JWT_PRIVATE_KEY')))
|
||||
algorithm = 'RS256'
|
||||
jwt = JWT::Multisig.generate_jwt jwt_payload, \
|
||||
{ Middlewares.application_name.to_sym => private_key },
|
||||
{ Middlewares.application_name.to_sym => algorithm }
|
||||
|
||||
[event_name, jwt]
|
||||
end
|
||||
end
|
||||
|
||||
class PrintToScreen
|
||||
def call(event_name, event_payload)
|
||||
Rails.logger.debug do
|
||||
['',
|
||||
'Produced new event at ' + Time.current.to_s + ': ',
|
||||
'name = ' + event_name,
|
||||
'payload = ' + event_payload.to_json,
|
||||
''].join("\n")
|
||||
end
|
||||
[event_name, event_payload]
|
||||
end
|
||||
end
|
||||
|
||||
class PublishToRabbitMQ
|
||||
extend Memoist
|
||||
|
||||
def call(event_name, event_payload)
|
||||
Rails.logger.debug do
|
||||
"\nPublishing #{routing_key(event_name)} (routing key) to #{exchange_name(event_name)} (exchange name).\n"
|
||||
end
|
||||
exchange = bunny_exchange(exchange_name(event_name))
|
||||
exchange.publish(event_payload.to_json, routing_key: routing_key(event_name))
|
||||
[event_name, event_payload]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bunny_session
|
||||
Bunny::Session.new(rabbitmq_credentials).tap do |session|
|
||||
session.start
|
||||
Kernel.at_exit { session.stop }
|
||||
end
|
||||
end
|
||||
memoize :bunny_session
|
||||
|
||||
def bunny_channel
|
||||
bunny_session.channel
|
||||
end
|
||||
memoize :bunny_channel
|
||||
|
||||
def bunny_exchange(name)
|
||||
bunny_channel.direct(name)
|
||||
end
|
||||
memoize :bunny_exchange
|
||||
|
||||
def rabbitmq_credentials
|
||||
return ENV['EVENT_API_RABBITMQ_URL'] if ENV['EVENT_API_RABBITMQ_URL'].present?
|
||||
|
||||
{ host: ENV.fetch('EVENT_API_RABBITMQ_HOST'),
|
||||
port: ENV.fetch('EVENT_API_RABBITMQ_PORT'),
|
||||
username: ENV.fetch('EVENT_API_RABBITMQ_USERNAME'),
|
||||
password: ENV.fetch('EVENT_API_RABBITMQ_PASSWORD') }
|
||||
end
|
||||
|
||||
def exchange_name(event_name)
|
||||
"#{Middlewares.application_name}.events.#{event_name.split('.').first}"
|
||||
end
|
||||
|
||||
def routing_key(event_name)
|
||||
event_name.split('.').drop(1).join('.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
middlewares << Middlewares::IncludeEventMetadata.new
|
||||
middlewares << Middlewares::GenerateJWT.new
|
||||
middlewares << Middlewares::PrintToScreen.new
|
||||
middlewares << Middlewares::PublishToRabbitMQ.new
|
||||
end
|
||||
46
lib/peatio/amqp/queue.rb
Normal file
46
lib/peatio/amqp/queue.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module AMQP
|
||||
class Queue
|
||||
|
||||
class <<self
|
||||
def connection
|
||||
@connection ||= ::Bunny.new(AMQP::Config.connect).tap do |conn|
|
||||
conn.start
|
||||
end
|
||||
end
|
||||
|
||||
def channel
|
||||
@channel ||= connection.create_channel
|
||||
end
|
||||
|
||||
def exchanges
|
||||
@exchanges ||= { default: channel.default_exchange }
|
||||
end
|
||||
|
||||
def exchange(id)
|
||||
exchanges[id] ||= channel.send *AMQP::Config.exchange(id)
|
||||
end
|
||||
|
||||
def publish(eid, payload, attrs={})
|
||||
payload = JSON.dump payload
|
||||
exchange(eid).publish(payload, attrs)
|
||||
end
|
||||
|
||||
# enqueue = publish to direct exchange
|
||||
def enqueue(id, payload, attrs={})
|
||||
eid = ::AMQP::Config.binding_exchange_id(id) || :default
|
||||
attrs.merge!({ routing_key: AMQP::Config.routing_key(id) })
|
||||
publish(eid, payload, attrs)
|
||||
end
|
||||
|
||||
def enqueue_event(type, id, event, payload, opts={})
|
||||
#::AMQP::Queue.enqueue_event("private", maker.uid, "trade", for_notify(maker))
|
||||
routing_key = [type, id, event].join('.')
|
||||
serialized_data = JSON.dump(payload)
|
||||
channel.exchange('peatio.events.ranger', type: 'topic').publish(serialized_data, routing_key: routing_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user