mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +02:00
PlaidConnectable concern
This commit is contained in:
parent
a268c5a563
commit
c8b1e1d059
6 changed files with 101 additions and 79 deletions
|
@ -2,8 +2,8 @@ class PlaidItemsController < ApplicationController
|
||||||
before_action :set_plaid_item, only: %i[destroy sync]
|
before_action :set_plaid_item, only: %i[destroy sync]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
Current.family.plaid_items.create_from_public_token(
|
Current.family.create_plaid_item!(
|
||||||
plaid_item_params[:public_token],
|
public_token: plaid_item_params[:public_token],
|
||||||
item_name: item_name,
|
item_name: item_name,
|
||||||
region: plaid_item_params[:region]
|
region: plaid_item_params[:region]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Family < ApplicationRecord
|
class Family < ApplicationRecord
|
||||||
include Syncable, AutoTransferMatchable, Subscribeable
|
include PlaidConnectable, Syncable, AutoTransferMatchable, Subscribeable
|
||||||
|
|
||||||
DATE_FORMATS = [
|
DATE_FORMATS = [
|
||||||
[ "MM-DD-YYYY", "%m-%d-%Y" ],
|
[ "MM-DD-YYYY", "%m-%d-%Y" ],
|
||||||
|
@ -15,7 +15,6 @@ class Family < ApplicationRecord
|
||||||
|
|
||||||
has_many :users, dependent: :destroy
|
has_many :users, dependent: :destroy
|
||||||
has_many :accounts, dependent: :destroy
|
has_many :accounts, dependent: :destroy
|
||||||
has_many :plaid_items, dependent: :destroy
|
|
||||||
has_many :invitations, dependent: :destroy
|
has_many :invitations, dependent: :destroy
|
||||||
|
|
||||||
has_many :imports, dependent: :destroy
|
has_many :imports, dependent: :destroy
|
||||||
|
@ -65,69 +64,10 @@ class Family < ApplicationRecord
|
||||||
@income_statement ||= IncomeStatement.new(self)
|
@income_statement ||= IncomeStatement.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
def sync_data(sync, start_date: nil)
|
|
||||||
# We don't rely on this value to guard the app, but keep it eventually consistent
|
|
||||||
sync_trial_status!
|
|
||||||
|
|
||||||
Rails.logger.info("Syncing accounts for family #{id}")
|
|
||||||
accounts.manual.each do |account|
|
|
||||||
account.sync_later(start_date: start_date, parent_sync: sync)
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.logger.info("Syncing plaid items for family #{id}")
|
|
||||||
plaid_items.each do |plaid_item|
|
|
||||||
plaid_item.sync_later(start_date: start_date, parent_sync: sync)
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.logger.info("Applying rules for family #{id}")
|
|
||||||
rules.each do |rule|
|
|
||||||
rule.apply_later
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_syncing_notice!
|
|
||||||
broadcast_remove target: "syncing-notice"
|
|
||||||
end
|
|
||||||
|
|
||||||
def post_sync(sync)
|
|
||||||
auto_match_transfers!
|
|
||||||
broadcast_refresh
|
|
||||||
end
|
|
||||||
|
|
||||||
# If family has any syncs pending/syncing within the last 10 minutes, we show a persistent "syncing" notice.
|
|
||||||
# Ignore syncs older than 10 minutes as they are considered "stale"
|
|
||||||
def syncing?
|
|
||||||
Sync.where(
|
|
||||||
"(syncable_type = 'Family' AND syncable_id = ?) OR
|
|
||||||
(syncable_type = 'Account' AND syncable_id IN (SELECT id FROM accounts WHERE family_id = ? AND plaid_account_id IS NULL)) OR
|
|
||||||
(syncable_type = 'PlaidItem' AND syncable_id IN (SELECT id FROM plaid_items WHERE family_id = ?))",
|
|
||||||
id, id, id
|
|
||||||
).where(status: [ "pending", "syncing" ], created_at: 10.minutes.ago..).exists?
|
|
||||||
end
|
|
||||||
|
|
||||||
def eu?
|
def eu?
|
||||||
country != "US" && country != "CA"
|
country != "US" && country != "CA"
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_link_token(webhooks_url:, redirect_url:, accountable_type: nil, region: :us, access_token: nil)
|
|
||||||
provider = if region.to_sym == :eu
|
|
||||||
Provider::Registry.get_provider(:plaid_eu)
|
|
||||||
else
|
|
||||||
Provider::Registry.get_provider(:plaid_us)
|
|
||||||
end
|
|
||||||
|
|
||||||
# early return when no provider
|
|
||||||
return nil unless provider
|
|
||||||
|
|
||||||
provider.get_link_token(
|
|
||||||
user_id: id,
|
|
||||||
webhooks_url: webhooks_url,
|
|
||||||
redirect_url: redirect_url,
|
|
||||||
accountable_type: accountable_type,
|
|
||||||
access_token: access_token
|
|
||||||
).link_token
|
|
||||||
end
|
|
||||||
|
|
||||||
def requires_data_provider?
|
def requires_data_provider?
|
||||||
# If family has any trades, they need a provider for historical prices
|
# If family has any trades, they need a provider for historical prices
|
||||||
return true if trades.any?
|
return true if trades.any?
|
||||||
|
|
51
app/models/family/plaid_connectable.rb
Normal file
51
app/models/family/plaid_connectable.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
module Family::PlaidConnectable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
has_many :plaid_items, dependent: :destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_plaid_item!(public_token:, item_name:, region:)
|
||||||
|
provider = plaid_provider_for_region(region)
|
||||||
|
|
||||||
|
public_token_response = provider.exchange_public_token(public_token)
|
||||||
|
|
||||||
|
plaid_item = plaid_items.create!(
|
||||||
|
name: item_name,
|
||||||
|
plaid_id: public_token_response.item_id,
|
||||||
|
access_token: public_token_response.access_token,
|
||||||
|
plaid_region: region
|
||||||
|
)
|
||||||
|
|
||||||
|
plaid_item.sync_later
|
||||||
|
|
||||||
|
plaid_item
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_link_token(webhooks_url:, redirect_url:, accountable_type: nil, region: :us, access_token: nil)
|
||||||
|
return nil unless plaid_us || plaid_eu
|
||||||
|
|
||||||
|
provider = plaid_provider_for_region(region)
|
||||||
|
|
||||||
|
provider.get_link_token(
|
||||||
|
user_id: self.id,
|
||||||
|
webhooks_url: webhooks_url,
|
||||||
|
redirect_url: redirect_url,
|
||||||
|
accountable_type: accountable_type,
|
||||||
|
access_token: access_token
|
||||||
|
).link_token
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def plaid_us
|
||||||
|
@plaid ||= Provider::Registry.get_provider(:plaid_us)
|
||||||
|
end
|
||||||
|
|
||||||
|
def plaid_eu
|
||||||
|
@plaid_eu ||= Provider::Registry.get_provider(:plaid_eu)
|
||||||
|
end
|
||||||
|
|
||||||
|
def plaid_provider_for_region(region)
|
||||||
|
region.to_sym == :eu ? plaid_eu : plaid_us
|
||||||
|
end
|
||||||
|
end
|
46
app/models/family/syncable.rb
Normal file
46
app/models/family/syncable.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
module Family::Syncable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# Top-level, generic Syncable concern
|
||||||
|
include ::Syncable
|
||||||
|
|
||||||
|
def sync_data(sync, start_date: nil)
|
||||||
|
# We don't rely on this value to guard the app, but keep it eventually consistent
|
||||||
|
sync_trial_status!
|
||||||
|
|
||||||
|
Rails.logger.info("Syncing accounts for family #{id}")
|
||||||
|
accounts.manual.each do |account|
|
||||||
|
account.sync_later(start_date: start_date, parent_sync: sync)
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("Syncing plaid items for family #{id}")
|
||||||
|
plaid_items.each do |plaid_item|
|
||||||
|
plaid_item.sync_later(start_date: start_date, parent_sync: sync)
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.info("Applying rules for family #{id}")
|
||||||
|
rules.each do |rule|
|
||||||
|
rule.apply_later
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_syncing_notice!
|
||||||
|
broadcast_remove target: "syncing-notice"
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_sync(sync)
|
||||||
|
auto_match_transfers!
|
||||||
|
broadcast_refresh
|
||||||
|
end
|
||||||
|
|
||||||
|
# If family has any syncs pending/syncing within the last 10 minutes, we show a persistent "syncing" notice.
|
||||||
|
# Ignore syncs older than 10 minutes as they are considered "stale"
|
||||||
|
def syncing?
|
||||||
|
Sync.where(
|
||||||
|
"(syncable_type = 'Family' AND syncable_id = ?) OR
|
||||||
|
(syncable_type = 'Account' AND syncable_id IN (SELECT id FROM accounts WHERE family_id = ? AND plaid_account_id IS NULL)) OR
|
||||||
|
(syncable_type = 'PlaidItem' AND syncable_id IN (SELECT id FROM plaid_items WHERE family_id = ?))",
|
||||||
|
id, id, id
|
||||||
|
).where(status: [ "pending", "syncing" ], created_at: 10.minutes.ago..).exists?
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,21 +22,6 @@ class PlaidItem < 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
|
|
||||||
def create_from_public_token(token, item_name:, region:)
|
|
||||||
response = plaid_provider_for_region(region).exchange_public_token(token)
|
|
||||||
|
|
||||||
new_plaid_item = create!(
|
|
||||||
name: item_name,
|
|
||||||
plaid_id: response.item_id,
|
|
||||||
access_token: response.access_token,
|
|
||||||
plaid_region: region
|
|
||||||
)
|
|
||||||
|
|
||||||
new_plaid_item.sync_later
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def sync_data(sync, start_date: nil)
|
def sync_data(sync, start_date: nil)
|
||||||
begin
|
begin
|
||||||
Rails.logger.info("Fetching and loading Plaid data")
|
Rails.logger.info("Fetching and loading Plaid data")
|
||||||
|
|
|
@ -8,7 +8,7 @@ class PlaidItemsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
|
||||||
test "create" do
|
test "create" do
|
||||||
@plaid_provider = mock
|
@plaid_provider = mock
|
||||||
PlaidItem.expects(:plaid_provider_for_region).with("us").returns(@plaid_provider)
|
Provider::Registry.expects(:get_provider).with(:plaid_us).returns(@plaid_provider)
|
||||||
|
|
||||||
public_token = "public-sandbox-1234"
|
public_token = "public-sandbox-1234"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue