Provider optimisation (#375)

* Implement a provider configured account type

* Fix for SimpleFIN

* FIX tests and linter
This commit is contained in:
soky srm
2025-11-24 19:52:34 +01:00
committed by GitHub
parent 4cd575fa23
commit 226207e2f7
27 changed files with 275 additions and 111 deletions

View File

@@ -31,6 +31,27 @@ class Provider::Base
raise NotImplementedError, "#{self.class} must implement #provider_name"
end
# Defines which account types this provider supports
# Override in subclasses to specify supported account types
# @return [Array<String>] Array of account type class names (e.g., ["Depository", "CreditCard"])
def self.supported_account_types
[]
end
# Returns provider connection configurations
# Override in subclasses to provide connection metadata for UI
# @param family [Family] The family to check connection availability for
# @return [Array<Hash>] Array of connection configurations with keys:
# - key: Unique identifier (e.g., "lunchflow", "plaid_us")
# - name: Display name (e.g., "Lunch Flow", "Plaid")
# - description: User-facing description
# - can_connect: Boolean, whether family can connect to this provider
# - new_account_path: Proc that generates path for new account flow
# - existing_account_path: Proc that generates path for linking existing account
def self.connection_configs(family:)
[]
end
# Returns the provider type (class name)
# @return [String] The provider account class name
def provider_type

View File

@@ -63,6 +63,39 @@ class Provider::Factory
find_adapter_class(provider_type).present?
end
# Get all registered adapter classes
# @return [Array<Class>] List of registered adapter classes
def registered_adapters
ensure_adapters_loaded
registry.values.uniq
end
# Get adapters that support a specific account type
# @param account_type [String] The account type class name (e.g., "Depository", "CreditCard")
# @return [Array<Class>] List of adapter classes that support this account type
def adapters_for_account_type(account_type)
registered_adapters.select do |adapter_class|
adapter_class.supported_account_types.include?(account_type)
end
end
# Check if any provider supports a given account type
# @param account_type [String] The account type class name
# @return [Boolean]
def supports_account_type?(account_type)
adapters_for_account_type(account_type).any?
end
# Get all available provider connection configs for a given account type
# @param account_type [String] The account type class name (e.g., "Depository")
# @param family [Family] The family to check connection availability for
# @return [Array<Hash>] Array of connection configurations from all providers
def connection_configs_for_account_type(account_type:, family:)
adapters_for_account_type(account_type).flat_map do |adapter_class|
adapter_class.connection_configs(family: family)
end
end
# Clear all registered adapters (useful for testing)
def clear_registry!
@registry = {}

View File

@@ -5,6 +5,34 @@ class Provider::LunchflowAdapter < Provider::Base
# Register this adapter with the factory
Provider::Factory.register("LunchflowAccount", self)
# Define which account types this provider supports
def self.supported_account_types
%w[Depository CreditCard Loan]
end
# Returns connection configurations for this provider
def self.connection_configs(family:)
return [] unless family.can_connect_lunchflow?
[ {
key: "lunchflow",
name: "Lunch Flow",
description: "Connect to your bank via Lunch Flow",
can_connect: true,
new_account_path: ->(accountable_type, return_to) {
Rails.application.routes.url_helpers.select_accounts_lunchflow_items_path(
accountable_type: accountable_type,
return_to: return_to
)
},
existing_account_path: ->(account_id) {
Rails.application.routes.url_helpers.select_existing_account_lunchflow_items_path(
account_id: account_id
)
}
} ]
end
def provider_name
"lunchflow"
end

View File

@@ -17,6 +17,63 @@ class Provider::PlaidAdapter < Provider::Base
# Register this adapter with the factory for ALL PlaidAccount instances
Provider::Factory.register("PlaidAccount", self)
# Define which account types this provider supports (US region)
def self.supported_account_types
%w[Depository CreditCard Loan Investment]
end
# Returns connection configurations for this provider
# Plaid can return multiple configs (US and EU) depending on family setup
def self.connection_configs(family:)
configs = []
# US configuration
if family.can_connect_plaid_us?
configs << {
key: "plaid_us",
name: "Plaid",
description: "Connect to your US bank via Plaid",
can_connect: true,
new_account_path: ->(accountable_type, return_to) {
Rails.application.routes.url_helpers.new_plaid_item_path(
region: "us",
accountable_type: accountable_type
)
},
existing_account_path: ->(account_id) {
Rails.application.routes.url_helpers.select_existing_account_plaid_items_path(
account_id: account_id,
region: "us"
)
}
}
end
# EU configuration
if family.can_connect_plaid_eu?
configs << {
key: "plaid_eu",
name: "Plaid (EU)",
description: "Connect to your EU bank via Plaid",
can_connect: true,
new_account_path: ->(accountable_type, return_to) {
Rails.application.routes.url_helpers.new_plaid_item_path(
region: "eu",
accountable_type: accountable_type
)
},
existing_account_path: ->(account_id) {
Rails.application.routes.url_helpers.select_existing_account_plaid_items_path(
account_id: account_id,
region: "eu"
)
}
}
end
configs
end
# Mutex for thread-safe configuration loading
# Initialized at class load time to avoid race conditions on mutex creation
@config_mutex = Mutex.new

View File

@@ -5,6 +5,33 @@ class Provider::SimplefinAdapter < Provider::Base
# Register this adapter with the factory
Provider::Factory.register("SimplefinAccount", self)
# Define which account types this provider supports
def self.supported_account_types
%w[Depository CreditCard Loan Investment]
end
# Returns connection configurations for this provider
def self.connection_configs(family:)
return [] unless family.can_connect_simplefin?
[ {
key: "simplefin",
name: "SimpleFIN",
description: "Connect to your bank via SimpleFIN",
can_connect: true,
new_account_path: ->(accountable_type, return_to) {
Rails.application.routes.url_helpers.new_simplefin_item_path(
accountable_type: accountable_type
)
},
existing_account_path: ->(account_id) {
Rails.application.routes.url_helpers.select_existing_account_simplefin_items_path(
account_id: account_id
)
}
} ]
end
def provider_name
"simplefin"
end