- Implement robust End User Agreement expiry detection and handling - Add graceful error recovery for failed accounts - Rewrite README.md to focus on user benefits - Add documentation guidelines to AGENTS.md
125 lines
5.5 KiB
Markdown
125 lines
5.5 KiB
Markdown
# Architecture Documentation
|
|
|
|
## Overview
|
|
|
|
Banks2FF implements a **Hexagonal (Ports & Adapters) Architecture** to synchronize bank transactions from GoCardless to Firefly III. This architecture separates business logic from external concerns, making the system testable and maintainable.
|
|
|
|
## Workspace Structure
|
|
|
|
```
|
|
banks2ff/
|
|
├── banks2ff/ # Main CLI application
|
|
│ └── src/
|
|
│ ├── core/ # Domain logic and models
|
|
│ ├── adapters/ # External service integrations
|
|
│ └── main.rs # CLI entry point
|
|
├── firefly-client/ # Firefly III API client library
|
|
├── gocardless-client/ # GoCardless API client library
|
|
└── docs/ # Architecture documentation
|
|
```
|
|
|
|
## Core Components
|
|
|
|
### 1. Domain Core (`banks2ff/src/core/`)
|
|
|
|
**models.rs**: Defines domain entities
|
|
- `BankTransaction`: Core transaction model with multi-currency support
|
|
- `Account`: Bank account representation
|
|
- Supports `foreign_amount` and `foreign_currency` for international transactions
|
|
|
|
**ports.rs**: Defines abstraction traits
|
|
- `TransactionSource`: Interface for fetching transactions (implemented by GoCardless adapter)
|
|
- `TransactionDestination`: Interface for storing transactions (implemented by Firefly adapter)
|
|
- Traits are mockable for isolated testing
|
|
|
|
**sync.rs**: Synchronization engine
|
|
- `run_sync()`: Orchestrates the entire sync process
|
|
- Implements "Healer" strategy for idempotency
|
|
- Smart date range calculation (Last Transaction Date + 1 to Yesterday)
|
|
|
|
### 2. Adapters (`banks2ff/src/adapters/`)
|
|
|
|
**gocardless/**: GoCardless integration
|
|
- `client.rs`: Wrapper for GoCardless client with token management
|
|
- `mapper.rs`: Converts GoCardless API responses to domain models
|
|
- `cache.rs`: Caches account mappings to reduce API calls
|
|
- Correctly handles multi-currency via `currencyExchange` array parsing
|
|
|
|
**firefly/**: Firefly III integration
|
|
- `client.rs`: Wrapper for Firefly client for transaction storage
|
|
- Maps domain models to Firefly API format
|
|
|
|
### 3. API Clients
|
|
|
|
Both clients are hand-crafted using `reqwest`:
|
|
- Strongly-typed DTOs for compile-time safety
|
|
- Custom error handling with `thiserror`
|
|
- Rate limit awareness and graceful degradation
|
|
|
|
## Synchronization Process
|
|
|
|
The "Healer" strategy ensures idempotency with robust error handling:
|
|
|
|
1. **Account Discovery**: Fetch active accounts from GoCardless (filtered by End User Agreement (EUA) validity)
|
|
2. **Agreement Validation**: Check EUA expiry status for each account's requisition
|
|
3. **Account Matching**: Match GoCardless accounts to Firefly asset accounts by IBAN
|
|
4. **Error-Aware Processing**: Continue with valid accounts when some have expired agreements
|
|
5. **Date Window**: Calculate sync range (Last Firefly transaction + 1 to Yesterday)
|
|
6. **Transaction Processing** (with error recovery):
|
|
- **Search**: Look for existing transaction using windowed heuristic (date ± 3 days, exact amount)
|
|
- **Heal**: If found without `external_id`, update with GoCardless transaction ID
|
|
- **Skip**: If found with matching `external_id`, ignore
|
|
- **Create**: If not found, create new transaction in Firefly
|
|
- **Error Handling**: Log issues but continue with other transactions/accounts
|
|
|
|
## Key Features
|
|
|
|
### Multi-Currency Support
|
|
- Parses `currencyExchange` array from GoCardless responses
|
|
- Calculates `foreign_amount = amount * exchange_rate`
|
|
- Maps to Firefly's `foreign_amount` and `foreign_currency_code` fields
|
|
|
|
### Rate Limit Management
|
|
- **Caching**: Stores `AccountId -> IBAN` mappings to reduce requisition calls
|
|
- **Token Reuse**: Maintains tokens until expiry to minimize auth requests
|
|
- **Graceful Handling**: Continues sync for other accounts when encountering 429 errors
|
|
|
|
### Agreement Expiry Handling
|
|
- **Proactive Validation**: Checks End User Agreement (EUA) expiry before making API calls to avoid unnecessary requests
|
|
- **Reactive Recovery**: Detects expired agreements from API 401 errors and skips affected accounts
|
|
- **Continued Operation**: Maintains partial sync success even when some accounts are inaccessible
|
|
- **User Feedback**: Provides detailed reporting on account status and re-authorization needs
|
|
- **Multiple Requisitions**: Supports accounts linked to multiple requisitions, using the most recent valid one
|
|
|
|
### Idempotency
|
|
- GoCardless `transactionId` → Firefly `external_id` mapping
|
|
- Windowed duplicate detection prevents double-creation
|
|
- Historical transaction healing for pre-existing data
|
|
|
|
## Data Flow
|
|
|
|
```
|
|
GoCardless API → GoCardlessAdapter → TransactionSource → SyncEngine → TransactionDestination → FireflyAdapter → Firefly API
|
|
```
|
|
|
|
## Testing Strategy
|
|
|
|
- **Unit Tests**: Core logic with `mockall` for trait mocking
|
|
- **Integration Tests**: API clients with `wiremock` for HTTP mocking
|
|
- **Fixture Testing**: Real JSON responses for adapter mapping validation
|
|
- **Isolation**: Business logic tested without external dependencies
|
|
|
|
## Error Handling
|
|
|
|
- **Custom Errors**: `thiserror` for domain-specific error types including End User Agreement (EUA) expiry (`SyncError::AgreementExpired`)
|
|
- **Propagation**: `anyhow` for error context across async boundaries
|
|
- **Graceful Degradation**: Rate limits, network issues, and expired agreements don't crash entire sync
|
|
- **Partial Success**: Continues processing available accounts when some fail
|
|
- **Structured Logging**: `tracing` for observability and debugging with account-level context
|
|
|
|
## Configuration Management
|
|
|
|
- Environment variables loaded via `dotenvy`
|
|
- Workspace-level dependency management
|
|
- Feature flags for optional functionality
|
|
- Secure credential handling (no hardcoded secrets) |