Initial commit

This commit is contained in:
Yaser
2026-08-13 19:50:53 +03:30
commit 38084458fe
879 changed files with 95198 additions and 0 deletions

62
lib/peatio/amqp/config.rb Normal file
View 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

View 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
View 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