From db878c109c8f1d487170af9d8286fc989bcb71cc Mon Sep 17 00:00:00 2001 From: Catherine Renelle <32781029+catrenelle@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:59:23 -0500 Subject: [PATCH] Update existing SimpleFIN connections on re-connect instead of creating duplicates When a user re-connects SimpleFIN (e.g. after a key change), match existing connections by institution name and update the access token in-place rather than always creating new SyncConnection records. New linked accounts are added if discovered, existing mappings are preserved. Co-Authored-By: Claude Opus 4.6 --- src/Purrse.Api/Services/BankSyncService.cs | 76 ++++++++++++++++------ 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/src/Purrse.Api/Services/BankSyncService.cs b/src/Purrse.Api/Services/BankSyncService.cs index 0dc3469..6b5f590 100644 --- a/src/Purrse.Api/Services/BankSyncService.cs +++ b/src/Purrse.Api/Services/BankSyncService.cs @@ -191,34 +191,72 @@ public class BankSyncService : IBankSyncService var accounts = await _simpleFin.GetAccountsAsync(config); var encryptedAccessUrl = _encryption.Encrypt(accessUrl); + // Load existing SimpleFIN connections for this user to avoid duplicates on re-connect + var existingConnections = await _db.SyncConnections + .Include(c => c.LinkedAccounts) + .Where(c => c.UserId == userId && c.Provider == SyncProvider.SimpleFIN) + .ToListAsync(); + var groupedByInstitution = accounts.GroupBy(a => a.Institution ?? "SimpleFIN Connection"); var connectionIds = new List(); foreach (var group in groupedByInstitution) { - var connection = new SyncConnection - { - Id = Guid.NewGuid(), - UserId = userId, - Provider = SyncProvider.SimpleFIN, - InstitutionName = group.Key, - EncryptedAccessToken = encryptedAccessUrl - }; + // Check if a connection already exists for this institution + var existing = existingConnections.FirstOrDefault(c => + c.InstitutionName == group.Key); - _db.SyncConnections.Add(connection); - connectionIds.Add(connection.Id); - - foreach (var account in group) + if (existing != null) { - _db.LinkedAccounts.Add(new LinkedAccount + // Update the existing connection with the new access token + existing.EncryptedAccessToken = encryptedAccessUrl; + existing.IsActive = true; + existing.UpdatedAt = DateTime.UtcNow; + connectionIds.Add(existing.Id); + + // Add any new linked accounts not already tracked + foreach (var account in group) + { + if (!existing.LinkedAccounts.Any(la => la.ExternalAccountId == account.ExternalId)) + { + _db.LinkedAccounts.Add(new LinkedAccount + { + Id = Guid.NewGuid(), + SyncConnectionId = existing.Id, + ExternalAccountId = account.ExternalId, + ExternalAccountName = account.Name, + ExternalAccountType = account.AccountType, + LastKnownBalance = account.Balance + }); + } + } + } + else + { + var connection = new SyncConnection { Id = Guid.NewGuid(), - SyncConnectionId = connection.Id, - ExternalAccountId = account.ExternalId, - ExternalAccountName = account.Name, - ExternalAccountType = account.AccountType, - LastKnownBalance = account.Balance - }); + UserId = userId, + Provider = SyncProvider.SimpleFIN, + InstitutionName = group.Key, + EncryptedAccessToken = encryptedAccessUrl + }; + + _db.SyncConnections.Add(connection); + connectionIds.Add(connection.Id); + + foreach (var account in group) + { + _db.LinkedAccounts.Add(new LinkedAccount + { + Id = Guid.NewGuid(), + SyncConnectionId = connection.Id, + ExternalAccountId = account.ExternalId, + ExternalAccountName = account.Name, + ExternalAccountType = account.AccountType, + LastKnownBalance = account.Balance + }); + } } }