Files
sure/app/models/coinbase_item/syncer.rb
LPW dd991fa339 Add Coinbase exchange integration with CDP API support (#704)
* **Add Coinbase integration with item and account management**
- Creates migrations for `coinbase_items` and `coinbase_accounts`.
- Adds models, controllers, views, and background tasks to support account linking, syncing, and transaction handling.
- Implements Coinbase API client and adapter for seamless integration.
- Supports ActiveRecord encryption for secure credential storage.
- Adds UI components for provider setup, account management, and synchronization.

* Localize Coinbase-related UI strings, refine account linking for security, and add timeouts to Coinbase API requests.

* Localize Coinbase account handling to support native currencies (USD, EUR, GBP, etc.) across balances, trades, holdings, and transactions.

* Improve Coinbase processing with timezone-safe parsing, native currency support, and immediate holdings updates.

* Improve trend percentage formatting and enhance race condition handling for Coinbase account linking.

* Fix log message wording for orphan cleanup

* Ensure `selected_accounts` parameter is sanitized by rejecting blank entries.

* Add tests for Coinbase integration: account, item, and controller coverage

- Adds unit tests for `CoinbaseAccount` and `CoinbaseItem` models.
- Adds integration tests for `CoinbaseItemsController`.
- Introduces Stimulus `select-all` controller for UI checkbox handling.
- Localizes UI strings and logging for Coinbase integration.

* Update test fixtures to use consistent placeholder API keys and secrets

* Refine `coinbase_item` tests to ensure deterministic ordering and improve scope assertions.

* Integrate `SyncStats::Collector` into Coinbase syncer to streamline statistics collection and enhance consistency.

* Localize Coinbase sync status messages and improve sync summary test coverage.

* Update `CoinbaseItem` encryption: use deterministic encryption for `api_key` and standard for `api_secret`.

* fix schema drift

* Beta labels to lower expectations

---------

Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-21 22:56:39 +01:00

93 lines
3.8 KiB
Ruby

# Orchestrates the sync process for a Coinbase connection.
# Imports data, processes accounts, and schedules account syncs.
class CoinbaseItem::Syncer
include SyncStats::Collector
attr_reader :coinbase_item
# @param coinbase_item [CoinbaseItem] Item to sync
def initialize(coinbase_item)
@coinbase_item = coinbase_item
end
# Runs the full sync workflow: import, process, and schedule.
# @param sync [Sync] Sync record for status tracking
def perform_sync(sync)
# Phase 1: Check credentials are configured
sync.update!(status_text: I18n.t("coinbase_item.syncer.checking_credentials")) if sync.respond_to?(:status_text)
unless coinbase_item.credentials_configured?
error_message = I18n.t("coinbase_item.syncer.credentials_invalid")
coinbase_item.update!(status: :requires_update)
mark_failed(sync, error_message)
return
end
# Phase 2: Import data from Coinbase API
sync.update!(status_text: I18n.t("coinbase_item.syncer.importing_accounts")) if sync.respond_to?(:status_text)
coinbase_item.import_latest_coinbase_data
# Phase 3: Check account setup status and collect sync statistics
sync.update!(status_text: I18n.t("coinbase_item.syncer.checking_configuration")) if sync.respond_to?(:status_text)
# Use SyncStats::Collector for consistent stats (checks current_account.present? by default)
collect_setup_stats(sync, provider_accounts: coinbase_item.coinbase_accounts.to_a)
unlinked_accounts = coinbase_item.coinbase_accounts.left_joins(:account_provider).where(account_providers: { id: nil })
linked_accounts = coinbase_item.coinbase_accounts.joins(:account_provider).joins(:account).merge(Account.visible)
if unlinked_accounts.any?
coinbase_item.update!(pending_account_setup: true)
sync.update!(status_text: I18n.t("coinbase_item.syncer.accounts_need_setup", count: unlinked_accounts.count)) if sync.respond_to?(:status_text)
else
coinbase_item.update!(pending_account_setup: false)
end
# Phase 4: Process holdings for linked accounts only
if linked_accounts.any?
sync.update!(status_text: I18n.t("coinbase_item.syncer.processing_accounts")) if sync.respond_to?(:status_text)
coinbase_item.process_accounts
# Phase 5: Schedule balance calculations for linked accounts
sync.update!(status_text: I18n.t("coinbase_item.syncer.calculating_balances")) if sync.respond_to?(:status_text)
coinbase_item.schedule_account_syncs(
parent_sync: sync,
window_start_date: sync.window_start_date,
window_end_date: sync.window_end_date
)
# Phase 6: Collect trade statistics
account_ids = linked_accounts.map { |ca| ca.current_account&.id }.compact
collect_transaction_stats(sync, account_ids: account_ids, source: "coinbase") if account_ids.any?
end
end
# Hook called after sync completion. Currently a no-op.
def perform_post_sync
# no-op
end
private
# Marks the sync as failed with an error message.
# Mirrors SimplefinItem::Syncer#mark_failed for consistent failure handling.
#
# @param sync [Sync] The sync record to mark as failed
# @param error_message [String] The error message to record
def mark_failed(sync, error_message)
if sync.respond_to?(:status) && sync.status.to_s == "completed"
Rails.logger.warn("CoinbaseItem::Syncer#mark_failed called after completion: #{error_message}")
return
end
sync.start! if sync.respond_to?(:may_start?) && sync.may_start?
if sync.respond_to?(:may_fail?) && sync.may_fail?
sync.fail!
elsif sync.respond_to?(:status)
sync.update!(status: :failed)
end
sync.update!(error: error_message) if sync.respond_to?(:error)
sync.update!(status_text: error_message) if sync.respond_to?(:status_text)
end
end