mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +02:00
feat(simplefin): Add rate limit
- You can now specify the rate limit so Maybe can't over do it with the backend calls - Fix various file placements from the updates - Adding an actual configuration class for SimpleFIN
This commit is contained in:
parent
d5acd4935a
commit
7b983fecb8
13 changed files with 102 additions and 152 deletions
|
@ -52,6 +52,7 @@ APP_DOMAIN=
|
||||||
# Allows configuration of SimpleFIN for account linking: https://www.simplefin.org/
|
# Allows configuration of SimpleFIN for account linking: https://www.simplefin.org/
|
||||||
# You'll want to follow the steps here for getting an AccessURL https://beta-bridge.simplefin.org/info/developers
|
# You'll want to follow the steps here for getting an AccessURL https://beta-bridge.simplefin.org/info/developers
|
||||||
SIMPLE_FIN_ACCESS_URL=
|
SIMPLE_FIN_ACCESS_URL=
|
||||||
|
SIMPLE_FIN_RATE_LIMIT=24 # How many queries we may perform to SimpleFIN a day
|
||||||
|
|
||||||
# Disable enforcing SSL connections
|
# Disable enforcing SSL connections
|
||||||
# DISABLE_SSL=true
|
# DISABLE_SSL=true
|
||||||
|
|
|
@ -12,7 +12,7 @@ class SimpleFinController < ApplicationController
|
||||||
@simple_fin_accounts = @simple_fin_accounts.filter { |acc| !account_exists(acc) }
|
@simple_fin_accounts = @simple_fin_accounts.filter { |acc| !account_exists(acc) }
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error "SimpleFIN: Failed to fetch accounts - #{e.message}"
|
Rails.logger.error "SimpleFIN: Failed to fetch accounts - #{e.message}"
|
||||||
redirect_to new_account_path, alert: t(".fetch_failed")
|
redirect_to root_path, alert: t(".fetch_failed")
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -26,7 +26,7 @@ class SimpleFinController < ApplicationController
|
||||||
def sync
|
def sync
|
||||||
@simple_fin_item = Current.family.simple_fin_items.find(params[:id])
|
@simple_fin_item = Current.family.simple_fin_items.find(params[:id])
|
||||||
unless @simple_fin_item.syncing?
|
unless @simple_fin_item.syncing?
|
||||||
@simple_fin_item.sync
|
@simple_fin_item.sync_later
|
||||||
end
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
@ -58,8 +58,6 @@ class SimpleFinController < ApplicationController
|
||||||
sfc.institution_name = org_details["name"]
|
sfc.institution_name = org_details["name"]
|
||||||
sfc.institution_url = org_details["url"]
|
sfc.institution_url = org_details["url"]
|
||||||
sfc.institution_domain = org_details["domain"]
|
sfc.institution_domain = org_details["domain"]
|
||||||
# TODO: Fix
|
|
||||||
sfc.last_sync_count_reset_at = Time.current # Mark as synced upon creation
|
|
||||||
end
|
end
|
||||||
|
|
||||||
sf_accounts_for_institution.each do |acc_detail|
|
sf_accounts_for_institution.each do |acc_detail|
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Provider::Registry
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_provider(name)
|
def get_provider(name)
|
||||||
|
puts "NAME: #{name}"
|
||||||
send(name)
|
send(name)
|
||||||
rescue NoMethodError
|
rescue NoMethodError
|
||||||
raise Error.new("Provider '#{name}' not found in registry")
|
raise Error.new("Provider '#{name}' not found in registry")
|
||||||
|
@ -65,7 +66,7 @@ class Provider::Registry
|
||||||
end
|
end
|
||||||
|
|
||||||
def simple_fin
|
def simple_fin
|
||||||
config = Rails.application.config.simple_fin
|
config = Provider::SimpleFin.provider_config
|
||||||
|
|
||||||
return nil unless config.present?
|
return nil unless config.present?
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
class Provider::SimpleFin
|
class Provider::SimpleFin
|
||||||
attr_reader :client, :region
|
attr_reader :client, :region
|
||||||
|
|
||||||
|
# Error subclasses
|
||||||
|
Error = Class.new(Provider::Error)
|
||||||
|
RateLimitExceededError = Class.new(Error)
|
||||||
|
|
||||||
# SimpleFIN only supports these account types
|
# SimpleFIN only supports these account types
|
||||||
MAYBE_SUPPORTED_SIMPLE_FIN_PRODUCTS = %w[Depository Investment Loan CreditCard].freeze
|
MAYBE_SUPPORTED_SIMPLE_FIN_PRODUCTS = %w[Depository Investment Loan CreditCard].freeze
|
||||||
# TODO
|
|
||||||
MAX_HISTORY_DAYS = 1
|
class << self
|
||||||
|
# Helper class method to access the SimpleFin specific configuration
|
||||||
|
def provider_config
|
||||||
|
Rails.application.config.simple_fin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(config, region: :us)
|
def initialize(config, region: :us)
|
||||||
@region = region
|
@region = region
|
||||||
|
@ -22,7 +31,7 @@ class Provider::SimpleFin
|
||||||
return false unless is_supported_account_type
|
return false unless is_supported_account_type
|
||||||
|
|
||||||
# Verify it is configured
|
# Verify it is configured
|
||||||
config = Rails.application.config.simple_fin
|
config = Provider::SimpleFin.provider_config
|
||||||
return false unless config.present?
|
return false unless config.present?
|
||||||
|
|
||||||
# Make sure this API version is supported
|
# Make sure this API version is supported
|
||||||
|
@ -37,8 +46,8 @@ class Provider::SimpleFin
|
||||||
# @param [Boolean] include_creds Controls if credentials should be included or if this request should be anonymous. Default true.
|
# @param [Boolean] include_creds Controls if credentials should be included or if this request should be anonymous. Default true.
|
||||||
def send_request_to_sf(path, include_creds = true)
|
def send_request_to_sf(path, include_creds = true)
|
||||||
# Grab access URL from the env
|
# Grab access URL from the env
|
||||||
config = Rails.application.config.simple_fin
|
config = Provider::SimpleFin.provider_config
|
||||||
access_url = config["ACCESS_URL"]
|
access_url = config.access_url
|
||||||
# Add the access URL to the path
|
# Add the access URL to the path
|
||||||
uri = URI.parse(access_url + path)
|
uri = URI.parse(access_url + path)
|
||||||
# Setup the request
|
# Setup the request
|
||||||
|
@ -77,6 +86,7 @@ class Provider::SimpleFin
|
||||||
# @param [int?] trans_end_date A linux epoch of the end date to get transactions between.
|
# @param [int?] trans_end_date A linux epoch of the end date to get transactions between.
|
||||||
# @param [Boolean] trans_pending If we should include pending transactions. Default is true.
|
# @param [Boolean] trans_pending If we should include pending transactions. Default is true.
|
||||||
def get_available_accounts(accountable_type, trans_start_date = nil, trans_end_date = nil, trans_pending = true)
|
def get_available_accounts(accountable_type, trans_start_date = nil, trans_end_date = nil, trans_pending = true)
|
||||||
|
check_rate_limit
|
||||||
endpoint = "/accounts?pending=#{trans_pending}"
|
endpoint = "/accounts?pending=#{trans_pending}"
|
||||||
|
|
||||||
# Add any parameters we care about
|
# Add any parameters we care about
|
||||||
|
@ -133,10 +143,30 @@ class Provider::SimpleFin
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Increments the call count for tracking rate limiting of SimpleFIN.
|
||||||
|
#
|
||||||
|
# @raises [RateLimitExceededError] if the daily API call limit has been reached.
|
||||||
|
def check_rate_limit
|
||||||
|
today = Date.current
|
||||||
|
# Find or initialize the rate limit record for the family for today
|
||||||
|
rate_limit_record = SimpleFinRateLimit.find_or_initialize_by(date: today)
|
||||||
|
|
||||||
|
# Determine the actual limit: from config
|
||||||
|
limit = Provider::SimpleFin.provider_config.rate_limit
|
||||||
|
|
||||||
|
if rate_limit_record.call_count >= limit
|
||||||
|
raise RateLimitExceededError, "SimpleFIN API daily rate limit exceeded. Limit: #{limit} calls."
|
||||||
|
end
|
||||||
|
|
||||||
|
# Increment the call count for today. This also saves the record if new or updates if existing.
|
||||||
|
rate_limit_record.update!(call_count: rate_limit_record.call_count + 1)
|
||||||
|
end
|
||||||
|
|
||||||
# Returns if this is a supported API of SimpleFIN by the access url in the config.
|
# Returns if this is a supported API of SimpleFIN by the access url in the config.
|
||||||
def is_supported_api
|
def is_supported_api
|
||||||
# Make sure the config is loaded since this is called early
|
# Make sure the config is loaded since this is called early
|
||||||
config = Rails.application.config.simple_fin
|
config = Provider::SimpleFin.provider_config
|
||||||
return false unless config.present?
|
return false unless config.present?
|
||||||
|
|
||||||
get_api_versions().include?("1.0")
|
get_api_versions().include?("1.0")
|
||||||
|
|
|
@ -21,6 +21,10 @@ class SimpleFinAccount < ApplicationRecord
|
||||||
class << self
|
class << self
|
||||||
def find_or_create_from_simple_fin_data!(sf_account_data, sfc)
|
def find_or_create_from_simple_fin_data!(sf_account_data, sfc)
|
||||||
sfc.simple_fin_accounts.find_or_create_by!(external_id: sf_account_data["id"]) do |sfa|
|
sfc.simple_fin_accounts.find_or_create_by!(external_id: sf_account_data["id"]) do |sfa|
|
||||||
|
sfa.current_balance = sf_account_data["balance"].to_d
|
||||||
|
sfa.available_balance = sf_account_data["balance"].to_d
|
||||||
|
sfa.currency = sf_account_data["currency"]
|
||||||
|
|
||||||
new_account = sfc.family.accounts.new(
|
new_account = sfc.family.accounts.new(
|
||||||
name: sf_account_data["name"],
|
name: sf_account_data["name"],
|
||||||
balance: sf_account_data["balance"].to_d,
|
balance: sf_account_data["balance"].to_d,
|
||||||
|
@ -64,16 +68,15 @@ class SimpleFinAccount < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Syncs all account data for the given sf_account_data parameter
|
||||||
# sf_account_data is a hash from Provider::SimpleFin#get_available_accounts
|
# sf_account_data is a hash from Provider::SimpleFin#get_available_accounts
|
||||||
def sync_account_data!(sf_account_data)
|
def sync_account_data!(sf_account_data)
|
||||||
# Ensure accountable_attributes has the ID for updates
|
|
||||||
# 'account' here refers to self.account (the associated Account instance)
|
|
||||||
accountable_attributes = { id: self.account.accountable_id }
|
accountable_attributes = { id: self.account.accountable_id }
|
||||||
self.update!(
|
self.update!(
|
||||||
current_balance: sf_account_data["balance"].to_d,
|
current_balance: sf_account_data["balance"].to_d,
|
||||||
available_balance: sf_account_data["available-balance"]&.to_d,
|
available_balance: sf_account_data["available-balance"]&.to_d,
|
||||||
currency: sf_account_data["currency"],
|
currency: sf_account_data["currency"],
|
||||||
# simple_fin_errors: sf_account_data["errors"] || [],
|
|
||||||
account_attributes: {
|
account_attributes: {
|
||||||
id: self.account.id,
|
id: self.account.id,
|
||||||
balance: sf_account_data["balance"].to_d,
|
balance: sf_account_data["balance"].to_d,
|
||||||
|
|
|
@ -16,86 +16,16 @@ class SimpleFinItem < ApplicationRecord
|
||||||
scope :ordered, -> { order(created_at: :desc) }
|
scope :ordered, -> { order(created_at: :desc) }
|
||||||
scope :needs_update, -> { where(status: :requires_update) }
|
scope :needs_update, -> { where(status: :requires_update) }
|
||||||
|
|
||||||
class << self
|
|
||||||
# # `provided_access_url` is the full URL from SimpleFIN (https://user:pass@beta-bridge.simplefin.org/simplefin)
|
|
||||||
# # `connection_name` can be user-provided or derived.
|
|
||||||
# def create_and_sync_from_access_url(provided_access_url, connection_name, family_obj)
|
|
||||||
# # Basic validation of the URL format
|
|
||||||
# uri = URI.parse(provided_access_url)
|
|
||||||
# raise ArgumentError, "Invalid SimpleFIN Access URL: Missing credentials" unless uri.user && uri.password
|
|
||||||
# raise ArgumentError, "Invalid SimpleFIN Access URL: Must be HTTPS" unless uri.scheme == "https"
|
|
||||||
|
|
||||||
# # Create the connection object first
|
|
||||||
# connection = family_obj.simple_fin_connections.create!(
|
|
||||||
# name: connection_name,
|
|
||||||
# access_url: provided_access_url,
|
|
||||||
# status: :good # Assume good initially
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Perform an initial sync to populate institution details and accounts
|
|
||||||
# connection.sync_later
|
|
||||||
# connection
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Syncs the simple_fin_item given and all other simple_fin accounts available (reduces calls to the API)
|
# Syncs the simple_fin_item given and all other simple_fin accounts available (reduces calls to the API)
|
||||||
def sync_data(sync, start_date: nil)
|
def sync_data(sync, start_date: nil)
|
||||||
# TODO: Rate limit
|
Rails.logger.info("SimpleFINItem: Starting sync for all SimpleFIN accounts")
|
||||||
# now = Time.current
|
|
||||||
# # Rate Limiting Check
|
|
||||||
# # We use a transaction here to ensure that checking the count, resetting it if needed,
|
|
||||||
# # and incrementing it are atomic.
|
|
||||||
# ActiveRecord::Base.transaction do
|
|
||||||
# # Reload self to ensure we have the latest values from the DB,
|
|
||||||
# # especially if this method could be called concurrently for the same item.
|
|
||||||
# self.reload
|
|
||||||
|
|
||||||
# if self.last_sync_count_reset_at.nil? || self.last_sync_count_reset_at.to_date < now.to_date
|
|
||||||
# # If it's a new day (or first sync ever for rate limiting), reset the count and the reset timestamp.
|
|
||||||
# self.update_columns(syncs_today_count: 0, last_sync_count_reset_at: now)
|
|
||||||
# self.reload # Reload again to get the just-updated values for the check below.
|
|
||||||
# end
|
|
||||||
|
|
||||||
# if self.syncs_today_count >= 24
|
|
||||||
# msg = "SimpleFinItem ID #{self.id}: Sync limit of 24 per day reached. Count: #{self.syncs_today_count}."
|
|
||||||
# Rails.logger.warn(msg)
|
|
||||||
# sync.fail!(StandardError.new(msg)) # Record failure in the Sync object
|
|
||||||
# raise StandardError, msg # Raise to stop execution and ensure SyncJob handles it as a failure
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # If not rate-limited, increment the count for this sync attempt.
|
|
||||||
# self.increment!(:syncs_today_count)
|
|
||||||
# end
|
|
||||||
|
|
||||||
|
|
||||||
# unless access_url.present?
|
|
||||||
# # This is a configuration error for the connection itself.
|
|
||||||
# msg = "SimpleFinConnection: Sync cannot proceed for connection ID #{id}: Missing access_url."
|
|
||||||
# Rails.logger.error(msg)
|
|
||||||
# update!(status: :requires_update) # Mark connection as needing attention
|
|
||||||
# # Raise an error to ensure the SyncJob records this failure.
|
|
||||||
# # Sync#perform will catch this and call sync.fail!
|
|
||||||
# raise StandardError, msg
|
|
||||||
# end
|
|
||||||
|
|
||||||
# TODO: Populate this
|
|
||||||
# update!(last_synced_at: Time.current, status: :requires_update)
|
|
||||||
|
|
||||||
Rails.logger.info("SimpleFinConnection: Starting sync for connection ID #{id}")
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# Fetch all accounts for this specific connection from SimpleFIN.
|
# Fetch all accounts for this specific connection from SimpleFIN.
|
||||||
sf_accounts_data = provider.get_available_accounts(nil)
|
sf_accounts_data = provider.get_available_accounts(nil)
|
||||||
|
# Iterate over every account and attempt to apply transactions where possible
|
||||||
|
|
||||||
# Keep track of external IDs reported by the provider in this sync.
|
|
||||||
# This can be used later to identify accounts that might have been removed on the SimpleFIN side.
|
|
||||||
current_provider_external_ids = []
|
|
||||||
|
|
||||||
sf_accounts_data.each do |sf_account_data|
|
sf_accounts_data.each do |sf_account_data|
|
||||||
current_provider_external_ids << sf_account_data["id"]
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# Find or create the SimpleFinAccount record.
|
# Find or create the SimpleFinAccount record.
|
||||||
sfa = SimpleFinAccount.find_by(external_id: sf_account_data["id"])
|
sfa = SimpleFinAccount.find_by(external_id: sf_account_data["id"])
|
||||||
|
@ -104,69 +34,27 @@ class SimpleFinItem < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
if sfa != nil
|
if sfa != nil
|
||||||
# Sync the detailed data for this account (e.g., balance, and potentially transactions).
|
begin
|
||||||
# This method is expected to be on the SimpleFinAccount model.
|
# Sync the detailed data for this account
|
||||||
sfa.sync_account_data!(sf_account_data)
|
sfa.sync_account_data!(sf_account_data)
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Optional: You could add logic here to handle accounts that exist in your DB for this
|
|
||||||
# SimpleFinConnection but were NOT reported by the provider in `sf_accounts_data`.
|
|
||||||
# These could be marked as closed, archived, etc. For example:
|
|
||||||
# simple_fin_accounts.where.not(external_id: current_provider_external_ids).find_each(&:archive!)
|
|
||||||
|
|
||||||
# update!(status: :good) # Mark connection as successfully synced.
|
|
||||||
Rails.logger.info("SimpleFinConnection: Sync completed for connection ID #{id}")
|
|
||||||
|
|
||||||
# rescue Provider::SimpleFin::AuthenticationError => e # Catch specific auth errors if your provider defines them.
|
|
||||||
# Rails.logger.error("SimpleFinConnection: Authentication failed for connection ID #{id}: #{e.message}")
|
|
||||||
# update!(status: :requires_update) # Mark the connection so the user knows to update credentials.
|
|
||||||
# raise e # Re-raise so Sync#perform can record the failure.
|
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error("SimpleFinConnection: Sync failed for connection ID #{id}: #{e.message}")
|
Rails.logger.error("SimpleFINItem: Sync failed for account #{sf_account_data["id"]}: #{e.message}")
|
||||||
update!(status: :requires_update)
|
sfa.simple_fin_item.update(id: sf_account_data["id"], status: :requires_update) # We had problems so make sure this account knows
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("SimpleFINItem: Sync completed for all accounts")
|
||||||
|
|
||||||
|
rescue Provider::SimpleFin::RateLimitExceededError =>e
|
||||||
|
Rails.logger.error("SimpleFINItem: Sync failed: #{e.message}")
|
||||||
|
raise StandardError, "SimpleFIN Rate Limit: #{e.message}" # Re-raise as a generic StandardError
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error("SimpleFINItem: Sync failed: #{e.message}")
|
||||||
raise e # Re-raise so Sync#perform can record the failure.
|
raise e # Re-raise so Sync#perform can record the failure.
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# def sync_data(sync, start_date: nil)
|
|
||||||
# update!(last_synced_at: Time.current)
|
|
||||||
# Rails.logger.info("SimpleFinConnection: Starting sync for connection ID #{id}")
|
|
||||||
|
|
||||||
# # begin
|
|
||||||
# # # Fetch initial info if not present (like institution details)
|
|
||||||
# # if institution_id.blank? || api_versions_supported.blank?
|
|
||||||
# # info_data = provider.get_api_versions_and_org_details_from_accounts
|
|
||||||
# # update!(
|
|
||||||
# # institution_id: info_data[:org_id],
|
|
||||||
# # institution_name: info_data[:org_name],
|
|
||||||
# # institution_url: info_data[:org_url],
|
|
||||||
# # institution_domain: info_data[:org_domain],
|
|
||||||
# # api_versions_supported: info_data[:versions]
|
|
||||||
# # )
|
|
||||||
# # end
|
|
||||||
|
|
||||||
# # sf_accounts_data = provider.get_available_accounts(nil) # Pass nil to get all types
|
|
||||||
|
|
||||||
# # sf_accounts_data.each do |sf_account_data|
|
|
||||||
# # accountable_klass_name = Provider::SimpleFin::ACCOUNTABLE_TYPE_MAPPING.find { |key, _val| sf_account_data["type"]&.downcase == key.downcase }&.last
|
|
||||||
# # accountable_klass_name ||= (sf_account_data["balance"].to_d >= 0 ? Depository : CreditCard) # Basic fallback
|
|
||||||
# # accountable_klass = accountable_klass_name
|
|
||||||
|
|
||||||
# # sfa = simple_fin_accounts.find_or_create_from_simple_fin_data!(sf_account_data, self, accountable_klass)
|
|
||||||
# # sfa.sync_account_data!(sf_account_data)
|
|
||||||
# # end
|
|
||||||
|
|
||||||
# update!(status: :good) if requires_update?
|
|
||||||
# Rails.logger.info("SimpleFinConnection: Sync completed for connection ID #{id}")
|
|
||||||
|
|
||||||
# # rescue StandardError => e
|
|
||||||
# # Rails.logger.error("SimpleFinConnection: Sync failed for connection ID #{id}: #{e.message}")
|
|
||||||
# # update!(status: :requires_update)
|
|
||||||
# # raise e
|
|
||||||
# # end
|
|
||||||
# end
|
|
||||||
|
|
||||||
def provider
|
def provider
|
||||||
@provider ||= Provider::Registry.get_provider(:simple_fin)
|
@provider ||= Provider::Registry.get_provider(:simple_fin)
|
||||||
end
|
end
|
||||||
|
|
4
app/models/simple_fin_rate_limit.rb
Normal file
4
app/models/simple_fin_rate_limit.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
class SimpleFinRateLimit < ApplicationRecord
|
||||||
|
validates :date, presence: true
|
||||||
|
validates :call_count, numericality: { greater_than_or_equal_to: 0 }
|
||||||
|
end
|
|
@ -1,13 +1,24 @@
|
||||||
require "ostruct"
|
|
||||||
|
class SimpleFinConfig
|
||||||
|
attr_accessor :access_url, :rate_limit, :max_history_days
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@rate_limit = 24
|
||||||
|
# TODO
|
||||||
|
@max_history_days = 1 # Was a constant, now part of config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Rails.application.configure do
|
Rails.application.configure do
|
||||||
config.simple_fin = nil
|
config.simple_fin = nil
|
||||||
|
|
||||||
|
# We use the access URL as the base of if we want to run SimpleFIN or not
|
||||||
if ENV["SIMPLE_FIN_ACCESS_URL"].present?
|
if ENV["SIMPLE_FIN_ACCESS_URL"].present?
|
||||||
config.simple_fin = OpenStruct.new()
|
|
||||||
config.simple_fin["ACCESS_URL"] = ENV["SIMPLE_FIN_ACCESS_URL"]
|
sf_config = SimpleFinConfig.new
|
||||||
config.simple_fin["UPDATE_CRON"] = ENV["SIMPLE_FIN_UPDATE_CRON"]
|
sf_config.access_url = ENV["SIMPLE_FIN_ACCESS_URL"]
|
||||||
# Fallback
|
sf_config.rate_limit = ENV["SIMPLE_FIN_RATE_LIMIT"].to_i if ENV["SIMPLE_FIN_RATE_LIMIT"].present?
|
||||||
config.simple_fin["UPDATE_CRON"] = "0 6 * * *" if config.simple_fin["UPDATE_CRON"].nil?
|
|
||||||
|
config.simple_fin = sf_config
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,8 @@ en:
|
||||||
simple_fin:
|
simple_fin:
|
||||||
form:
|
form:
|
||||||
add_accounts: "Add Selected Accounts"
|
add_accounts: "Add Selected Accounts"
|
||||||
|
new:
|
||||||
|
fetch_failed: "Failed to fetch accounts"
|
||||||
create:
|
create:
|
||||||
accounts_created_success: "Accounts Successfully Created"
|
accounts_created_success: "Accounts Successfully Created"
|
||||||
fetch_failed: "Failed to fetch accounts"
|
fetch_failed: "Failed to fetch accounts"
|
||||||
|
|
|
@ -9,9 +9,6 @@ class SimpleFinIntegration < ActiveRecord::Migration[7.2]
|
||||||
t.string :status, default: "good" # e.g., good, requires_update
|
t.string :status, default: "good" # e.g., good, requires_update
|
||||||
t.string :institution_errors, array: true, default: []
|
t.string :institution_errors, array: true, default: []
|
||||||
t.boolean :scheduled_for_deletion, default: false
|
t.boolean :scheduled_for_deletion, default: false
|
||||||
# Columns for rate limiting
|
|
||||||
t.integer :syncs_today_count, default: 0, null: false
|
|
||||||
t.datetime :last_sync_count_reset_at
|
|
||||||
|
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
@ -37,5 +34,13 @@ class SimpleFinIntegration < ActiveRecord::Migration[7.2]
|
||||||
add_column :holdings, :simple_fin_holding_id, :string
|
add_column :holdings, :simple_fin_holding_id, :string
|
||||||
add_index :holdings, :simple_fin_holding_id, unique: true, where: "simple_fin_holding_id IS NOT NULL"
|
add_index :holdings, :simple_fin_holding_id, unique: true, where: "simple_fin_holding_id IS NOT NULL"
|
||||||
add_column :holdings, :source, :string
|
add_column :holdings, :source, :string
|
||||||
|
|
||||||
|
create_table :simple_fin_rate_limits, id: :uuid do |t|
|
||||||
|
t.date :date, null: false
|
||||||
|
t.integer :call_count, null: false, default: 0
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :simple_fin_rate_limits, [ :date ], unique: true, name: 'index_sfrl_on_date'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
10
db/schema.rb
generated
10
db/schema.rb
generated
|
@ -580,13 +580,19 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_09_134646) do
|
||||||
t.string "status", default: "good"
|
t.string "status", default: "good"
|
||||||
t.string "institution_errors", default: [], array: true
|
t.string "institution_errors", default: [], array: true
|
||||||
t.boolean "scheduled_for_deletion", default: false
|
t.boolean "scheduled_for_deletion", default: false
|
||||||
t.integer "syncs_today_count", default: 0, null: false
|
|
||||||
t.datetime "last_sync_count_reset_at"
|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["family_id"], name: "index_simple_fin_items_on_family_id"
|
t.index ["family_id"], name: "index_simple_fin_items_on_family_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "simple_fin_rate_limits", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
t.date "date", null: false
|
||||||
|
t.integer "call_count", default: 0, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["date"], name: "index_sfrl_on_date", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
create_table "stock_exchanges", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "stock_exchanges", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "acronym"
|
t.string "acronym"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"errors": [
|
"errors": [
|
||||||
"Connection to Fidelity Investments may need attention"
|
"Connection to Fidelity Investments may need attention",
|
||||||
|
"Connection to Wright-Patt Credit Union may need attention"
|
||||||
],
|
],
|
||||||
"accounts": [
|
"accounts": [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue