mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 12:04:08 +00:00
Lunchflow fix (#307)
* Fix lunch flow pre-loading and UX * Small UX fixes - Proper closing of modal on cancel - Preload on new account already * Review comments * Fix json error * Delete .claude/settings.local.json Signed-off-by: soky srm <sokysrm@gmail.com> * Lunch Flow brand (again :-) * FIX process only linked accounts * FIX disable accounts with no name * Fix string normalization --------- Signed-off-by: soky srm <sokysrm@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
This commit is contained in:
@@ -37,7 +37,8 @@ class LunchflowItem < ApplicationRecord
|
||||
return [] if lunchflow_accounts.empty?
|
||||
|
||||
results = []
|
||||
lunchflow_accounts.joins(:account).each do |lunchflow_account|
|
||||
# Only process accounts that are linked and have active status
|
||||
lunchflow_accounts.joins(:account).merge(Account.visible).each do |lunchflow_account|
|
||||
begin
|
||||
result = LunchflowAccount::Processor.new(lunchflow_account).process
|
||||
results << { lunchflow_account_id: lunchflow_account.id, success: true, result: result }
|
||||
@@ -55,7 +56,8 @@ class LunchflowItem < ApplicationRecord
|
||||
return [] if accounts.empty?
|
||||
|
||||
results = []
|
||||
accounts.each do |account|
|
||||
# Only schedule syncs for active accounts
|
||||
accounts.visible.each do |account|
|
||||
begin
|
||||
account.sync_later(
|
||||
parent_sync: parent_sync,
|
||||
|
||||
@@ -24,31 +24,39 @@ class LunchflowItem::Importer
|
||||
# Continue with import even if snapshot storage fails
|
||||
end
|
||||
|
||||
# Step 2: Import accounts
|
||||
accounts_imported = 0
|
||||
# Step 2: Update only previously selected accounts (don't create new ones)
|
||||
accounts_updated = 0
|
||||
accounts_failed = 0
|
||||
|
||||
if accounts_data[:accounts].present?
|
||||
# Get all existing lunchflow account IDs for this item (normalize to strings for comparison)
|
||||
existing_account_ids = lunchflow_item.lunchflow_accounts.pluck(:account_id).map(&:to_s)
|
||||
|
||||
accounts_data[:accounts].each do |account_data|
|
||||
account_id = account_data[:id]&.to_s
|
||||
next unless account_id.present?
|
||||
|
||||
# Only update if this account was previously selected (exists in our DB)
|
||||
next unless existing_account_ids.include?(account_id)
|
||||
|
||||
begin
|
||||
import_account(account_data)
|
||||
accounts_imported += 1
|
||||
accounts_updated += 1
|
||||
rescue => e
|
||||
accounts_failed += 1
|
||||
account_id = account_data[:id] || "unknown"
|
||||
Rails.logger.error "LunchflowItem::Importer - Failed to import account #{account_id}: #{e.message}"
|
||||
# Continue importing other accounts even if one fails
|
||||
Rails.logger.error "LunchflowItem::Importer - Failed to update account #{account_id}: #{e.message}"
|
||||
# Continue updating other accounts even if one fails
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Rails.logger.info "LunchflowItem::Importer - Imported #{accounts_imported} accounts (#{accounts_failed} failed)"
|
||||
Rails.logger.info "LunchflowItem::Importer - Updated #{accounts_updated} accounts (#{accounts_failed} failed)"
|
||||
|
||||
# Step 3: Fetch transactions for each account
|
||||
# Step 3: Fetch transactions only for linked accounts with active status
|
||||
transactions_imported = 0
|
||||
transactions_failed = 0
|
||||
|
||||
lunchflow_item.lunchflow_accounts.each do |lunchflow_account|
|
||||
lunchflow_item.lunchflow_accounts.joins(:account).merge(Account.visible).each do |lunchflow_account|
|
||||
begin
|
||||
result = fetch_and_store_transactions(lunchflow_account)
|
||||
if result[:success]
|
||||
@@ -63,11 +71,11 @@ class LunchflowItem::Importer
|
||||
end
|
||||
end
|
||||
|
||||
Rails.logger.info "LunchflowItem::Importer - Completed import for item #{lunchflow_item.id}: #{accounts_imported} accounts, #{transactions_imported} transactions"
|
||||
Rails.logger.info "LunchflowItem::Importer - Completed import for item #{lunchflow_item.id}: #{accounts_updated} accounts updated, #{transactions_imported} transactions"
|
||||
|
||||
{
|
||||
success: accounts_failed == 0 && transactions_failed == 0,
|
||||
accounts_imported: accounts_imported,
|
||||
accounts_updated: accounts_updated,
|
||||
accounts_failed: accounts_failed,
|
||||
transactions_imported: transactions_imported,
|
||||
transactions_failed: transactions_failed
|
||||
@@ -123,16 +131,23 @@ class LunchflowItem::Importer
|
||||
|
||||
account_id = account_data[:id]
|
||||
|
||||
# Validate required account_id to prevent duplicate creation
|
||||
# Validate required account_id
|
||||
if account_id.blank?
|
||||
Rails.logger.warn "LunchflowItem::Importer - Skipping account with missing ID"
|
||||
raise ArgumentError, "Account ID is required"
|
||||
end
|
||||
|
||||
lunchflow_account = lunchflow_item.lunchflow_accounts.find_or_initialize_by(
|
||||
# Only find existing accounts, don't create new ones during sync
|
||||
lunchflow_account = lunchflow_item.lunchflow_accounts.find_by(
|
||||
account_id: account_id.to_s
|
||||
)
|
||||
|
||||
# Skip if account wasn't previously selected
|
||||
unless lunchflow_account
|
||||
Rails.logger.debug "LunchflowItem::Importer - Skipping unselected account #{account_id}"
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
lunchflow_account.upsert_lunchflow_snapshot!(account_data)
|
||||
lunchflow_account.save!
|
||||
|
||||
@@ -13,7 +13,7 @@ class LunchflowItem::Syncer
|
||||
# Phase 2: Check account setup status and collect sync statistics
|
||||
sync.update!(status_text: "Checking account configuration...") if sync.respond_to?(:status_text)
|
||||
total_accounts = lunchflow_item.lunchflow_accounts.count
|
||||
linked_accounts = lunchflow_item.lunchflow_accounts.joins(:account)
|
||||
linked_accounts = lunchflow_item.lunchflow_accounts.joins(:account).merge(Account.visible)
|
||||
unlinked_accounts = lunchflow_item.lunchflow_accounts.includes(:account).where(accounts: { id: nil })
|
||||
|
||||
# Store sync statistics for display
|
||||
|
||||
@@ -21,10 +21,10 @@ class Provider::Lunchflow
|
||||
|
||||
handle_response(response)
|
||||
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
|
||||
Rails.logger.error "Lunchflow API: GET /accounts failed: #{e.class}: #{e.message}"
|
||||
Rails.logger.error "Lunch Flow API: GET /accounts failed: #{e.class}: #{e.message}"
|
||||
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
|
||||
rescue => e
|
||||
Rails.logger.error "Lunchflow API: Unexpected error during GET /accounts: #{e.class}: #{e.message}"
|
||||
Rails.logger.error "Lunch Flow API: Unexpected error during GET /accounts: #{e.class}: #{e.message}"
|
||||
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
|
||||
end
|
||||
|
||||
@@ -52,10 +52,10 @@ class Provider::Lunchflow
|
||||
|
||||
handle_response(response)
|
||||
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
|
||||
Rails.logger.error "Lunchflow API: GET #{path} failed: #{e.class}: #{e.message}"
|
||||
Rails.logger.error "Lunch Flow API: GET #{path} failed: #{e.class}: #{e.message}"
|
||||
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
|
||||
rescue => e
|
||||
Rails.logger.error "Lunchflow API: Unexpected error during GET #{path}: #{e.class}: #{e.message}"
|
||||
Rails.logger.error "Lunch Flow API: Unexpected error during GET #{path}: #{e.class}: #{e.message}"
|
||||
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
|
||||
end
|
||||
|
||||
@@ -71,10 +71,10 @@ class Provider::Lunchflow
|
||||
|
||||
handle_response(response)
|
||||
rescue SocketError, Net::OpenTimeout, Net::ReadTimeout => e
|
||||
Rails.logger.error "Lunchflow API: GET #{path} failed: #{e.class}: #{e.message}"
|
||||
Rails.logger.error "Lunch Flow API: GET #{path} failed: #{e.class}: #{e.message}"
|
||||
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
|
||||
rescue => e
|
||||
Rails.logger.error "Lunchflow API: Unexpected error during GET #{path}: #{e.class}: #{e.message}"
|
||||
Rails.logger.error "Lunch Flow API: Unexpected error during GET #{path}: #{e.class}: #{e.message}"
|
||||
raise LunchflowError.new("Exception during GET request: #{e.message}", :request_failed)
|
||||
end
|
||||
|
||||
@@ -93,7 +93,7 @@ class Provider::Lunchflow
|
||||
when 200
|
||||
JSON.parse(response.body, symbolize_names: true)
|
||||
when 400
|
||||
Rails.logger.error "Lunchflow API: Bad request - #{response.body}"
|
||||
Rails.logger.error "Lunch Flow API: Bad request - #{response.body}"
|
||||
raise LunchflowError.new("Bad request to Lunchflow API: #{response.body}", :bad_request)
|
||||
when 401
|
||||
raise LunchflowError.new("Invalid API key", :unauthorized)
|
||||
@@ -104,7 +104,7 @@ class Provider::Lunchflow
|
||||
when 429
|
||||
raise LunchflowError.new("Rate limit exceeded. Please try again later.", :rate_limited)
|
||||
else
|
||||
Rails.logger.error "Lunchflow API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
|
||||
Rails.logger.error "Lunch Flow API: Unexpected response - Code: #{response.code}, Body: #{response.body}"
|
||||
raise LunchflowError.new("Failed to fetch data: #{response.code} #{response.message} - #{response.body}", :fetch_failed)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,12 +6,12 @@ class Provider::LunchflowAdapter < Provider::Base
|
||||
# Register this adapter with the factory
|
||||
Provider::Factory.register("LunchflowAccount", self)
|
||||
|
||||
# Configuration for Lunchflow
|
||||
# Configuration for Lunch Flow
|
||||
configure do
|
||||
description <<~DESC
|
||||
Setup instructions:
|
||||
1. Visit [Lunchflow](https://www.lunchflow.app) to get your API key
|
||||
2. Enter your API key below to enable Lunchflow bank data sync
|
||||
1. Visit [Lunch Flow](https://www.lunchflow.app) to get your API key
|
||||
2. Enter your API key below to enable Lunch Flow bank data sync
|
||||
3. Choose the appropriate environment (production or staging)
|
||||
DESC
|
||||
|
||||
@@ -20,21 +20,21 @@ class Provider::LunchflowAdapter < Provider::Base
|
||||
required: true,
|
||||
secret: true,
|
||||
env_key: "LUNCHFLOW_API_KEY",
|
||||
description: "Your Lunchflow API key for authentication"
|
||||
description: "Your Lunch Flow API key for authentication"
|
||||
|
||||
field :base_url,
|
||||
label: "Base URL",
|
||||
required: false,
|
||||
env_key: "LUNCHFLOW_BASE_URL",
|
||||
default: "https://lunchflow.app/api/v1",
|
||||
description: "Base URL for Lunchflow API"
|
||||
description: "Base URL for Lunch Flow API"
|
||||
end
|
||||
|
||||
def provider_name
|
||||
"lunchflow"
|
||||
end
|
||||
|
||||
# Build a Lunchflow provider instance with configured credentials
|
||||
# Build a Lunch Flow provider instance with configured credentials
|
||||
# @return [Provider::Lunchflow, nil] Returns nil if API key is not configured
|
||||
def self.build_provider
|
||||
api_key = config_value(:api_key)
|
||||
@@ -46,7 +46,7 @@ class Provider::LunchflowAdapter < Provider::Base
|
||||
|
||||
# Reload Lunchflow configuration when settings are updated
|
||||
def self.reload_configuration
|
||||
# Lunchflow doesn't need to configure Rails.application.config like Plaid does
|
||||
# Lunch Flow doesn't need to configure Rails.application.config like Plaid does
|
||||
# The configuration is read dynamically via config_value(:api_key) and config_value(:base_url)
|
||||
# This method exists to be called by the settings controller after updates
|
||||
# No action needed here since values are fetched on-demand
|
||||
@@ -65,7 +65,7 @@ class Provider::LunchflowAdapter < Provider::Base
|
||||
end
|
||||
|
||||
def institution_domain
|
||||
# Lunchflow may provide institution metadata in account data
|
||||
# Lunch Flow may provide institution metadata in account data
|
||||
metadata = provider_account.institution_metadata
|
||||
return nil unless metadata.present?
|
||||
|
||||
@@ -77,7 +77,7 @@ class Provider::LunchflowAdapter < Provider::Base
|
||||
begin
|
||||
domain = URI.parse(url).host&.gsub(/^www\./, "")
|
||||
rescue URI::InvalidURIError
|
||||
Rails.logger.warn("Invalid institution URL for Lunchflow account #{provider_account.id}: #{url}")
|
||||
Rails.logger.warn("Invalid institution URL for Lunch Flow account #{provider_account.id}: #{url}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user