Implement Requested Fixes for CoinStats Integration (#621)

* Fix(CoinStats): Add provider network exception handling

* Fix(CoinStats): Don't expose HTTP response to user

* Fix(CoinStats): Migrate syncer strings to locale files
This commit is contained in:
Ethan
2026-01-12 02:27:00 -06:00
committed by GitHub
parent fa78e1d292
commit d354ce48e1
3 changed files with 46 additions and 14 deletions

View File

@@ -12,11 +12,11 @@ class CoinstatsItem::Syncer
# @param sync [Sync] Sync record for status tracking
def perform_sync(sync)
# Phase 1: Import data from CoinStats API
sync.update!(status_text: "Importing wallets from CoinStats...") if sync.respond_to?(:status_text)
sync.update!(status_text: I18n.t("models.coinstats_item.syncer.importing_wallets")) if sync.respond_to?(:status_text)
coinstats_item.import_latest_coinstats_data
# Phase 2: Check account setup status and collect sync statistics
sync.update!(status_text: "Checking wallet configuration...") if sync.respond_to?(:status_text)
sync.update!(status_text: I18n.t("models.coinstats_item.syncer.checking_configuration")) if sync.respond_to?(:status_text)
total_accounts = coinstats_item.coinstats_accounts.count
linked_accounts = coinstats_item.coinstats_accounts.joins(:account_provider).joins(:account).merge(Account.visible)
@@ -30,18 +30,18 @@ class CoinstatsItem::Syncer
if unlinked_accounts.any?
coinstats_item.update!(pending_account_setup: true)
sync.update!(status_text: "#{unlinked_accounts.count} wallets need setup...") if sync.respond_to?(:status_text)
sync.update!(status_text: I18n.t("models.coinstats_item.syncer.wallets_need_setup", count: unlinked_accounts.count)) if sync.respond_to?(:status_text)
else
coinstats_item.update!(pending_account_setup: false)
end
# Phase 3: Process holdings for linked accounts only
if linked_accounts.any?
sync.update!(status_text: "Processing holdings...") if sync.respond_to?(:status_text)
sync.update!(status_text: I18n.t("models.coinstats_item.syncer.processing_holdings")) if sync.respond_to?(:status_text)
coinstats_item.process_accounts
# Phase 4: Schedule balance calculations for linked accounts
sync.update!(status_text: "Calculating balances...") if sync.respond_to?(:status_text)
sync.update!(status_text: I18n.t("models.coinstats_item.syncer.calculating_balances")) if sync.respond_to?(:status_text)
coinstats_item.schedule_account_syncs(
parent_sync: sync,
window_start_date: sync.window_start_date,

View File

@@ -25,6 +25,9 @@ class Provider::Coinstats < Provider
res = self.class.get("#{BASE_URL}/wallet/blockchains", headers: auth_headers)
handle_response(res)
end
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "CoinStats API: GET /wallet/blockchains failed: #{e.class}: #{e.message}"
raise Error, "CoinStats API request failed: #{e.message}"
end
# Returns blockchain options formatted for select dropdowns
@@ -75,6 +78,9 @@ class Provider::Coinstats < Provider
)
handle_response(res)
end
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "CoinStats API: GET /wallet/balances failed: #{e.class}: #{e.message}"
raise Error, "CoinStats API request failed: #{e.message}"
end
# Extract balance data for a specific wallet from bulk response
@@ -114,6 +120,9 @@ class Provider::Coinstats < Provider
)
handle_response(res)
end
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
Rails.logger.error "CoinStats API: GET /wallet/transactions failed: #{e.class}: #{e.message}"
raise Error, "CoinStats API request failed: #{e.message}"
end
# Extract transaction data for a specific wallet from bulk response
@@ -162,23 +171,36 @@ class Provider::Coinstats < Provider
when 200
JSON.parse(response.body, symbolize_names: true)
when 400
raise Error, "CoinStats: #{response.code} Bad Request - Invalid parameters or request format #{response.body}"
log_api_error(response, "Bad Request")
raise Error, "CoinStats: Invalid request parameters"
when 401
raise Error, "CoinStats: #{response.code} Unauthorized - Invalid or missing API key #{response.body}"
log_api_error(response, "Unauthorized")
raise Error, "CoinStats: Invalid or missing API key"
when 403
raise Error, "CoinStats: #{response.code} Forbidden - #{response.body}"
log_api_error(response, "Forbidden")
raise Error, "CoinStats: Access denied"
when 404
raise Error, "CoinStats: #{response.code} Not Found - Resource not found #{response.body}"
log_api_error(response, "Not Found")
raise Error, "CoinStats: Resource not found"
when 409
raise Error, "CoinStats: #{response.code} Conflict - Resource conflict #{response.body}"
log_api_error(response, "Conflict")
raise Error, "CoinStats: Resource conflict"
when 429
raise Error, "CoinStats: #{response.code} Too Many Requests - Rate limit exceeded #{response.body}"
log_api_error(response, "Too Many Requests")
raise Error, "CoinStats: Rate limit exceeded, try again later"
when 500
raise Error, "CoinStats: #{response.code} Internal Server Error - Server error #{response.body}"
log_api_error(response, "Internal Server Error")
raise Error, "CoinStats: Server error, try again later"
when 503
raise Error, "CoinStats: #{response.code} Service Unavailable - #{response.body}"
log_api_error(response, "Service Unavailable")
raise Error, "CoinStats: Service temporarily unavailable"
else
raise Error, "CoinStats: #{response.code} Unexpected Error - #{response.body}"
log_api_error(response, "Unexpected Error")
raise Error, "CoinStats: An unexpected error occurred"
end
end
def log_api_error(response, error_type)
Rails.logger.error "CoinStats API: #{response.code} #{error_type} - #{response.body}"
end
end

View File

@@ -0,0 +1,10 @@
---
en:
models:
coinstats_item:
syncer:
importing_wallets: Importing wallets from CoinStats...
checking_configuration: Checking wallet configuration...
wallets_need_setup: "%{count} wallets need setup..."
processing_holdings: Processing holdings...
calculating_balances: Calculating balances...