mirror of
https://github.com/we-promise/sure.git
synced 2026-04-18 19:44:09 +00:00
* [FEATURE] Add CoinStats exchange portfolios and normalize linked investment charts * [BUGFIX] Fix CoinStats PR regressions * [BUGFIX] Fix CoinStats PR review findings * [BUGFIX] Address follow-up CoinStats PR feedback * [REFACTO] Extract CoinStats exchange account helpers * [BUGFIX] Batch linked CoinStats chart normalization * [BUGFIX] Fix CoinStats processor lint --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
99 lines
3.6 KiB
Ruby
99 lines
3.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class CoinstatsItem::ExchangeLinker
|
|
Result = Struct.new(:success?, :created_count, :errors, keyword_init: true)
|
|
|
|
attr_reader :coinstats_item, :connection_id, :connection_fields, :name
|
|
|
|
def initialize(coinstats_item, connection_id:, connection_fields:, name: nil)
|
|
@coinstats_item = coinstats_item
|
|
@connection_id = connection_id
|
|
@connection_fields = connection_fields.to_h.compact_blank
|
|
@name = name
|
|
end
|
|
|
|
def link
|
|
return Result.new(success?: false, created_count: 0, errors: [ "Exchange is required" ]) if connection_id.blank?
|
|
return Result.new(success?: false, created_count: 0, errors: [ "Exchange credentials are required" ]) if connection_fields.blank?
|
|
|
|
created_count = 0
|
|
exchange = fetch_exchange_definition
|
|
validate_required_fields!(exchange)
|
|
|
|
response = provider.connect_portfolio_exchange(
|
|
connection_id: connection_id,
|
|
connection_fields: connection_fields,
|
|
name: name.presence || default_portfolio_name(exchange)
|
|
)
|
|
|
|
return Result.new(success?: false, created_count: 0, errors: [ response.error.message ]) unless response.success?
|
|
|
|
payload = response.data.with_indifferent_access
|
|
portfolio_id = payload[:portfolioId]
|
|
raise Provider::Coinstats::Error, "CoinStats did not return a portfolioId" if portfolio_id.blank?
|
|
|
|
coins = provider.list_portfolio_coins(portfolio_id: portfolio_id)
|
|
|
|
ActiveRecord::Base.transaction do
|
|
coinstats_item.update!(
|
|
exchange_connection_id: connection_id,
|
|
exchange_portfolio_id: portfolio_id,
|
|
institution_id: connection_id,
|
|
institution_name: exchange[:name],
|
|
raw_institution_payload: exchange
|
|
)
|
|
|
|
if coins.nil?
|
|
Rails.logger.warn "CoinstatsItem::ExchangeLinker - Initial portfolio coin fetch missing for item #{coinstats_item.id} portfolio #{portfolio_id}; deferring local account creation to background sync"
|
|
else
|
|
coinstats_account = exchange_portfolio_account_manager.upsert_account!(
|
|
coins_data: coins,
|
|
portfolio_id: portfolio_id,
|
|
connection_id: exchange[:connection_id],
|
|
exchange_name: exchange[:name],
|
|
account_name: name.presence || exchange[:name],
|
|
institution_logo: exchange[:icon]
|
|
)
|
|
created_count = exchange_portfolio_account_manager.ensure_local_account!(coinstats_account) ? 1 : 0
|
|
end
|
|
end
|
|
|
|
coinstats_item.sync_later
|
|
|
|
Result.new(success?: true, created_count: created_count, errors: [])
|
|
rescue Provider::Coinstats::Error, ArgumentError => e
|
|
Result.new(success?: false, created_count: 0, errors: [ e.message ])
|
|
end
|
|
|
|
private
|
|
def provider
|
|
@provider ||= Provider::Coinstats.new(coinstats_item.api_key)
|
|
end
|
|
|
|
def exchange_portfolio_account_manager
|
|
@exchange_portfolio_account_manager ||= CoinstatsItem::ExchangePortfolioAccountManager.new(coinstats_item)
|
|
end
|
|
|
|
def fetch_exchange_definition
|
|
exchange = provider.exchange_options.find { |option| option[:connection_id] == connection_id }
|
|
raise ArgumentError, "Unsupported exchange connection: #{connection_id}" unless exchange
|
|
|
|
exchange
|
|
end
|
|
|
|
def validate_required_fields!(exchange)
|
|
missing_fields = Array(exchange[:connection_fields]).filter_map do |field|
|
|
key = field[:key].to_s
|
|
field[:name] if key.blank? || connection_fields[key].blank?
|
|
end
|
|
|
|
return if missing_fields.empty?
|
|
|
|
raise ArgumentError, "Missing required exchange fields: #{missing_fields.join(', ')}"
|
|
end
|
|
|
|
def default_portfolio_name(exchange)
|
|
"#{exchange[:name]} Portfolio"
|
|
end
|
|
end
|