mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-08 06:55:21 +02:00
Merge remote-tracking branch 'forked/main' into simple-fin-integration
This commit is contained in:
commit
676c609725
14 changed files with 37 additions and 125 deletions
|
@ -7,7 +7,6 @@ module AutoSync
|
|||
|
||||
private
|
||||
def sync_family
|
||||
Current.family.update!(last_synced_at: Time.current)
|
||||
Current.family.sync_later
|
||||
end
|
||||
|
||||
|
|
|
@ -93,8 +93,6 @@ class Account < ApplicationRecord
|
|||
end
|
||||
|
||||
def sync_data(sync, start_date: nil)
|
||||
update!(last_synced_at: Time.current)
|
||||
|
||||
Rails.logger.info("Processing balances (#{linked? ? 'reverse' : 'forward'})")
|
||||
sync_balances
|
||||
end
|
||||
|
|
|
@ -27,7 +27,11 @@ module Syncable
|
|||
end
|
||||
|
||||
def sync_error
|
||||
latest_sync.error
|
||||
latest_sync&.error
|
||||
end
|
||||
|
||||
def last_synced_at
|
||||
latest_sync&.last_ran_at
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -67,8 +67,6 @@ class Family < ApplicationRecord
|
|||
end
|
||||
|
||||
def sync_data(sync, start_date: nil)
|
||||
update!(last_synced_at: Time.current)
|
||||
|
||||
# We don't rely on this value to guard the app, but keep it eventually consistent
|
||||
sync_trial_status!
|
||||
|
||||
|
@ -77,16 +75,6 @@ class Family < ApplicationRecord
|
|||
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("Syncing simple_fin items for family #{id}")
|
||||
simple_fin_items.each do |simple_fin_item|
|
||||
simple_fin_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
|
||||
|
|
|
@ -38,8 +38,6 @@ class PlaidItem < ApplicationRecord
|
|||
end
|
||||
|
||||
def sync_data(sync, start_date: nil)
|
||||
update!(last_synced_at: Time.current)
|
||||
|
||||
begin
|
||||
Rails.logger.info("Fetching and loading Plaid data")
|
||||
fetch_and_load_plaid_data(sync)
|
||||
|
|
|
@ -21,51 +21,17 @@ class Sync < ApplicationRecord
|
|||
begin
|
||||
syncable.sync_data(self, start_date: start_date)
|
||||
|
||||
unless has_pending_child_syncs?
|
||||
complete!
|
||||
Rails.logger.info("Sync completed, starting post-sync")
|
||||
syncable.post_sync(self)
|
||||
Rails.logger.info("Post-sync completed")
|
||||
end
|
||||
rescue StandardError => error
|
||||
fail! error
|
||||
raise error if Rails.env.development?
|
||||
ensure
|
||||
notify_parent_of_completion! if has_parent?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_child_completion_event
|
||||
Sync.transaction do
|
||||
# We need this to ensure 2 child syncs don't update the parent at the exact same time with different results
|
||||
# and cause the sync to hang in "syncing" status indefinitely
|
||||
self.lock!
|
||||
|
||||
unless has_pending_child_syncs?
|
||||
complete!
|
||||
|
||||
# If this sync is both a child and a parent, we need to notify the parent of completion
|
||||
notify_parent_of_completion! if has_parent?
|
||||
|
||||
Rails.logger.info("Sync completed, starting post-sync")
|
||||
syncable.post_sync(self)
|
||||
Rails.logger.info("Post-sync completed")
|
||||
rescue StandardError => error
|
||||
fail! error, report_error: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def has_pending_child_syncs?
|
||||
children.where(status: [ :pending, :syncing ]).any?
|
||||
end
|
||||
|
||||
def has_parent?
|
||||
parent_id.present?
|
||||
end
|
||||
|
||||
def notify_parent_of_completion!
|
||||
parent.handle_child_completion_event
|
||||
end
|
||||
|
||||
def start!
|
||||
Rails.logger.info("Starting sync")
|
||||
update! status: :syncing
|
||||
|
@ -76,12 +42,14 @@ class Sync < ApplicationRecord
|
|||
update! status: :completed, last_ran_at: Time.current
|
||||
end
|
||||
|
||||
def fail!(error)
|
||||
def fail!(error, report_error: false)
|
||||
Rails.logger.error("Sync failed: #{error.message}")
|
||||
|
||||
Sentry.capture_exception(error) do |scope|
|
||||
scope.set_context("sync", { id: id, syncable_type: syncable_type, syncable_id: syncable_id })
|
||||
scope.set_tags(sync_id: id)
|
||||
if report_error
|
||||
Sentry.capture_exception(error) do |scope|
|
||||
scope.set_context("sync", { id: id, syncable_type: syncable_type, syncable_id: syncable_id })
|
||||
scope.set_tags(sync_id: id)
|
||||
end
|
||||
end
|
||||
|
||||
update!(
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
<h1 class="text-xl"><%= t(".accounts") %></h1>
|
||||
<div class="flex items-center gap-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Sync all",
|
||||
href: sync_all_accounts_path,
|
||||
method: :post,
|
||||
variant: "outline",
|
||||
disabled: Current.family.syncing?,
|
||||
icon: "refresh-cw",
|
||||
class: ""
|
||||
) %>
|
||||
<% if Rails.env.development? %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Sync all",
|
||||
href: sync_all_accounts_path,
|
||||
method: :post,
|
||||
variant: "outline",
|
||||
disabled: Current.family.syncing?,
|
||||
icon: "refresh-cw",
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<%= render LinkComponent.new(
|
||||
text: "New account",
|
||||
|
|
|
@ -28,13 +28,12 @@
|
|||
<% end %>
|
||||
|
||||
<div class="flex items-center gap-1 ml-auto">
|
||||
<% if account.plaid_account_id.present? %>
|
||||
<% if Rails.env.development? %>
|
||||
<%= icon(
|
||||
<% if Rails.env.development? %>
|
||||
<%= icon(
|
||||
"refresh-cw",
|
||||
as_button: true,
|
||||
size: "sm",
|
||||
href: sync_plaid_item_path(account.plaid_account.plaid_item),
|
||||
href: account.linked? ? sync_plaid_item_path(account.plaid_account.plaid_item) : sync_account_path(account),
|
||||
disabled: account.syncing?,
|
||||
frame: :_top,
|
||||
title: "Refresh all SimpleFIN accounts"
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% elsif Rails.env.development? %>
|
||||
<%= icon(
|
||||
"refresh-cw",
|
||||
as_button: true,
|
||||
|
|
7
db/migrate/20250509182903_dynamic_last_synced.rb
Normal file
7
db/migrate/20250509182903_dynamic_last_synced.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class DynamicLastSynced < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
remove_column :plaid_items, :last_synced_at, :datetime
|
||||
remove_column :accounts, :last_synced_at, :datetime
|
||||
remove_column :families, :last_synced_at, :datetime
|
||||
end
|
||||
end
|
5
db/schema.rb
generated
5
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_05_09_134646) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_05_09_182903) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
@ -34,7 +34,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_09_134646) do
|
|||
t.uuid "import_id"
|
||||
t.uuid "plaid_account_id"
|
||||
t.boolean "scheduled_for_deletion", default: false
|
||||
t.datetime "last_synced_at"
|
||||
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
|
||||
t.jsonb "locked_attributes", default: {}
|
||||
t.bigint "simple_fin_account_id"
|
||||
|
@ -230,7 +229,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_09_134646) do
|
|||
t.string "stripe_customer_id"
|
||||
t.string "date_format", default: "%m-%d-%Y"
|
||||
t.string "country", default: "US"
|
||||
t.datetime "last_synced_at"
|
||||
t.string "timezone"
|
||||
t.boolean "data_enrichment_enabled", default: false
|
||||
t.boolean "early_access", default: false
|
||||
|
@ -453,7 +451,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_09_134646) do
|
|||
t.datetime "updated_at", null: false
|
||||
t.string "available_products", default: [], array: true
|
||||
t.string "billed_products", default: [], array: true
|
||||
t.datetime "last_synced_at"
|
||||
t.string "plaid_region", default: "us", null: false
|
||||
t.string "institution_url"
|
||||
t.string "institution_id"
|
||||
|
|
3
test/fixtures/families.yml
vendored
3
test/fixtures/families.yml
vendored
|
@ -1,8 +1,5 @@
|
|||
empty:
|
||||
name: Family
|
||||
last_synced_at: <%= Time.now %>
|
||||
|
||||
dylan_family:
|
||||
name: The Dylan Family
|
||||
last_synced_at: <%= Time.now %>
|
||||
|
||||
|
|
|
@ -20,10 +20,6 @@ class FamilyTest < ActiveSupport::TestCase
|
|||
.with(start_date: nil, parent_sync: family_sync)
|
||||
.times(manual_accounts_count)
|
||||
|
||||
PlaidItem.any_instance.expects(:sync_later)
|
||||
.with(start_date: nil, parent_sync: family_sync)
|
||||
.times(items_count)
|
||||
|
||||
@syncable.sync_data(family_sync, start_date: family_sync.start_date)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,44 +31,4 @@ class SyncTest < ActiveSupport::TestCase
|
|||
assert_equal "failed", @sync.status
|
||||
assert_equal "test sync error", @sync.error
|
||||
end
|
||||
|
||||
# Order is important here. Parent syncs must implement sync_data so that their own work
|
||||
# is 100% complete *prior* to queueing up child syncs.
|
||||
test "runs sync with child syncs" do
|
||||
family = families(:dylan_family)
|
||||
|
||||
parent = Sync.create!(syncable: family)
|
||||
child1 = Sync.create!(syncable: family.accounts.first, parent: parent)
|
||||
child2 = Sync.create!(syncable: family.accounts.second, parent: parent)
|
||||
grandchild = Sync.create!(syncable: family.accounts.last, parent: child2)
|
||||
|
||||
parent.syncable.expects(:sync_data).returns([]).once
|
||||
child1.syncable.expects(:sync_data).returns([]).once
|
||||
child2.syncable.expects(:sync_data).returns([]).once
|
||||
grandchild.syncable.expects(:sync_data).returns([]).once
|
||||
|
||||
assert_equal "pending", parent.status
|
||||
assert_equal "pending", child1.status
|
||||
assert_equal "pending", child2.status
|
||||
assert_equal "pending", grandchild.status
|
||||
|
||||
parent.perform
|
||||
assert_equal "syncing", parent.reload.status
|
||||
|
||||
child1.perform
|
||||
assert_equal "completed", child1.reload.status
|
||||
assert_equal "syncing", parent.reload.status
|
||||
|
||||
child2.perform
|
||||
assert_equal "syncing", child2.reload.status
|
||||
assert_equal "completed", child1.reload.status
|
||||
assert_equal "syncing", parent.reload.status
|
||||
|
||||
# Will complete the parent and grandparent syncs
|
||||
grandchild.perform
|
||||
assert_equal "completed", grandchild.reload.status
|
||||
assert_equal "completed", child1.reload.status
|
||||
assert_equal "completed", child2.reload.status
|
||||
assert_equal "completed", parent.reload.status
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue