Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,11 @@ RUB, USD, BTC, LTC, ETH, DSH, KZT, XRP, ETC, XMR, BCH, EUR, NEO, ZEC
- Factory Bot for test data in `factories/`
- VCR for HTTP request mocking
- Database Rewinder for fast test cleanup
- Sidekiq testing inline enabled

## File Organization

- `app/models/gera/` - Core domain models
- `app/workers/gera/` - Background job workers
- `lib/gera/` - Core engine logic and utilities
- `lib/builders/` - Rate calculation builders
- `spec/` - Test suite with dummy app
- `spec/` - Test suite with dummy app
32 changes: 15 additions & 17 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ GIT
PATH
remote: .
specs:
gera (0.4.0)
gera (1.2.0)
active_link_to
alias_association
authority
Expand All @@ -35,8 +35,8 @@ PATH
request_store
require_all
rest-client (~> 2.0)
sidekiq
simple_form
solid_queue
virtus

GEM
Expand Down Expand Up @@ -179,12 +179,17 @@ GEM
ruby2_keywords
drb (2.2.3)
erubi (1.13.1)
et-orbi (1.4.0)
tzinfo
factory_bot (6.5.6)
activesupport (>= 6.1.0)
ffi (1.17.2)
ffi (1.17.2-x86_64-linux-gnu)
formatador (1.2.3)
reline
fugit (1.12.1)
et-orbi (~> 1.4)
raabro (~> 1.4)
globalid (1.3.0)
activesupport (>= 6.1)
guard (2.19.1)
Expand Down Expand Up @@ -261,7 +266,6 @@ GEM
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0924)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (5.26.2)
monetize (1.13.0)
money (~> 6.12)
Expand All @@ -286,9 +290,6 @@ GEM
net-protocol
netrc (0.11.0)
nio4r (2.7.5)
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.10-x86_64-linux-gnu)
racc (~> 1.4)
notiffany (0.1.3)
Expand Down Expand Up @@ -319,6 +320,7 @@ GEM
pry (>= 0.13.0)
psych (3.1.0)
public_suffix (7.0.0)
raabro (1.4.0)
racc (1.8.1)
rack (3.2.4)
rack-session (2.1.1)
Expand Down Expand Up @@ -364,8 +366,6 @@ GEM
rb-inotify (0.11.1)
ffi (~> 1.0)
rdoc (6.3.4.1)
redis-client (0.26.1)
connection_pool
regexp_parser (2.11.3)
reline (0.6.3)
io-console (~> 0.5)
Expand Down Expand Up @@ -420,17 +420,16 @@ GEM
ruby2_keywords (0.0.5)
securerandom (0.4.1)
shellany (0.0.1)
sidekiq (8.0.9)
connection_pool (>= 2.5.0)
json (>= 2.9.0)
logger (>= 1.6.2)
rack (>= 3.1.0)
redis-client (>= 0.23.2)
simple_form (5.4.0)
actionpack (>= 7.0)
activemodel (>= 7.0)
sqlite3 (2.8.0)
mini_portile2 (~> 2.8.0)
solid_queue (1.2.4)
activejob (>= 7.1)
activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1)
fugit (~> 1.11)
railties (>= 7.1)
thor (>= 1.3.1)
sqlite3 (2.8.0-x86_64-linux-gnu)
thor (1.4.0)
thread_safe (0.3.6)
Expand Down Expand Up @@ -464,7 +463,6 @@ GEM
zeitwerk (2.7.3)

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def activate
CurrencyRateModeSnapshot.status_active.update_all status: :deactive
snapshot.update status: :active
end
CurrencyRatesWorker.perform_async if Rails.env.production?
CurrencyRatesJob.perform_later if Rails.env.production?
flash[:success] = 'Режимы активированы'
redirect_to currency_rate_mode_snapshot_path snapshot
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class DirectionRateHistoryIntervalsController < ApplicationController
authorize_actions_for DirectionRate
helper_method :payment_system_from, :payment_system_to
helper_method :filter
helper_method :history_intervals_enabled?

def index
respond_to do |format|
Expand Down Expand Up @@ -58,5 +59,9 @@ def intervals
raise "Unknown value_type #{filter.value_type}"
end
end

def history_intervals_enabled?
Gera.enable_direction_rate_history_intervals
end
end
end
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# frozen_string_literal: true

require 'open-uri'
require 'rest-client'

module Gera
module RatesWorker
module RatesJob
extend ActiveSupport::Concern

Error = Class.new(StandardError)

def perform
logger.debug "RatesWorker: before perform for #{rate_source.class.name}"
logger.debug "RatesJob: before perform for #{rate_source.class.name}"
ActiveRecord::Base.connection.clear_query_cache

@rates = load_rates
Expand Down Expand Up @@ -48,15 +49,15 @@ def save_all_rates
hash[pair_str] = { 'buy' => buy_price.to_f, 'sell' => sell_price.to_f }
end

ExternalRatesBatchWorker.perform_async(
ExternalRatesBatchJob.perform_later(
rate_source_snapshot.id,
rate_source.id,
batched_rates
)
end

def rate_keys
raise NotImplementedError, 'You must define #rate_keys in your worker'
raise NotImplementedError, 'You must define #rate_keys in your job'
end
end
end
2 changes: 2 additions & 0 deletions app/jobs/gera/application_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@

module Gera
class ApplicationJob < ActiveJob::Base
# SolidQueue's engine automatically includes ActiveJob::ConcurrencyControls
# which provides limits_concurrency method
end
end
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# frozen_string_literal: true

module Gera
class BinanceRatesWorker
include Sidekiq::Worker
class BinanceRatesJob < ApplicationJob
include AutoLogger
include RatesWorker
include RatesJob

sidekiq_options lock: :until_executed
limits_concurrency to: 1, key: ->(*) { 'gera_binance_rates' }, duration: 1.minute

def perform
# Check if we should approve new rates based on count
unless should_approve_new_rates?
logger.debug "BinanceRatesWorker: Rate counts don't match, skipping"
logger.debug "BinanceRatesJob: Rate counts don't match, skipping"
return nil
end

Expand Down Expand Up @@ -39,7 +38,7 @@ def should_approve_new_rates?
current_rates_count = rate_source.actual_snapshot.external_rates.count
new_rates_count = load_rates.size

logger.info "BinanceRatesWorker: current_rates_count=#{current_rates_count}, new_rates_count=#{new_rates_count}"
logger.info "BinanceRatesJob: current_rates_count=#{current_rates_count}, new_rates_count=#{new_rates_count}"

# Only approve if counts match
current_rates_count == new_rates_count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
module Gera
# Import rates from Bitfinex
#
class BitfinexRatesWorker
include Sidekiq::Worker
class BitfinexRatesJob < ApplicationJob
include AutoLogger
include RatesWorker
include RatesJob

private

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
module Gera
# Import rates from Bybit
#
class BybitRatesWorker
include Sidekiq::Worker
class BybitRatesJob < ApplicationJob
include AutoLogger
include RatesWorker
include RatesJob

private

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# frozen_string_literal: true

module Gera
class CbrAvgRatesWorker
include Sidekiq::Worker
class CbrAvgRatesJob < ApplicationJob
include AutoLogger

sidekiq_options lock: :until_executed
limits_concurrency to: 1, key: ->(*) { 'gera_cbr_avg_rates' }, duration: 1.minute

def perform
ActiveRecord::Base.connection.clear_query_cache
Expand All @@ -18,7 +17,7 @@ def perform
end

private

def source
@source ||= Gera::RateSourceCbrAvg.get!
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
# frozen_string_literal: true

require 'open-uri'
require 'business_time'

module Gera
# Import rates from Russian Central Bank
# http://www.cbr.ru/scripts/XML_daily.asp?date_req=08/04/2018
#
class CbrRatesWorker
include Sidekiq::Worker
class CbrRatesJob < ApplicationJob
include AutoLogger

# sidekiq_options lock: :until_executed
# limits_concurrency to: 1, key: -> { 'gera_cbr_rates' }, duration: 1.minute

CURRENCIES = %w[USD KZT EUR UAH UZS AZN BYN TRY THB IDR].freeze

Expand All @@ -36,18 +34,18 @@ class CbrRatesWorker
URL = 'https://pay.hub.pp.ru/api/cbr'

def perform
logger.debug 'CbrRatesWorker: before perform'
logger.debug 'CbrRatesJob: before perform'
ActiveRecord::Base.connection.clear_query_cache
rates_by_date = load_rates
logger.debug 'CbrRatesWorker: before transaction'
logger.debug 'CbrRatesJob: before transaction'
ActiveRecord::Base.transaction do
rates_by_date.each do |date, rates|
save_rates(date, rates)
end
end
logger.debug 'CbrRatesWorker: after transaction'
logger.debug 'CbrRatesJob: after transaction'
make_snapshot
logger.debug 'CbrRatesWorker: after perform'
logger.debug 'CbrRatesJob: after perform'
end

private
Expand Down Expand Up @@ -146,11 +144,11 @@ def load_rates
rates_by_date[date] = fetch_rates(date)
rescue WrongDate => err
logger.warn err

# HTTP redirection loop: http://www.cbr.ru/scripts/XML_daily.asp?date_req=09/01/2019
rescue RuntimeError => err
raise err unless err.message.include? 'HTTP redirection loop'

logger.error err
end
rates_by_date
Expand All @@ -162,7 +160,7 @@ def fetch_rates(date)

logger.info "fetch rates for #{date} from #{uri}"

doc = Nokogiri::XML open uri
doc = Nokogiri::XML URI.open(uri)
root = doc.xpath('/ValCurs')

root_date = root.attr('Date').text
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
# frozen_string_literal: true

module Gera
class CreateHistoryIntervalsWorker
include Sidekiq::Worker
class CreateHistoryIntervalsJob < ApplicationJob
include AutoLogger

sidekiq_options lock: :until_executed
limits_concurrency to: 1, key: ->(*) { 'gera_create_history_intervals' }, duration: 1.hour

MAXIMAL_DATE = 30.minutes
MINIMAL_DATE = Time.parse('13-07-2018 18:00')

def perform
save_direction_rate_history_intervals if Gera::DirectionRateHistoryInterval.table_exists?
if Gera::DirectionRateHistoryInterval.table_exists?
if Gera.enable_direction_rate_history_intervals
save_direction_rate_history_intervals
else
logger.info 'Skipping direction_rate_history_intervals creation (disabled by config)'
end
end
save_currency_rate_history_intervals if Gera::CurrencyRateHistoryInterval.table_exists?
end

private

def lock_timeout
1.hours * 1000
end

def save_direction_rate_history_intervals
last_saved_interval = Gera::DirectionRateHistoryInterval.maximum(:interval_to)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# frozen_string_literal: true

module Gera
class CryptomusRatesWorker
include Sidekiq::Worker
class CryptomusRatesJob < ApplicationJob
include AutoLogger
include RatesWorker
include RatesJob

private

Expand Down
Loading
Loading