Compare commits
11 Commits
master
...
49a975c43c
| Author | SHA1 | Date | |
|---|---|---|---|
| 49a975c43c | |||
| 9442d71e84 | |||
| d185ca36fd | |||
| 74d362b412 | |||
| a4fcea1afe | |||
| cf5e6eee08 | |||
| f7e96bcf35 | |||
| 6f293730ee | |||
| fcd59b7fc5 | |||
| 4b5dc6f59a | |||
| 6903ba6924 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
|||||||
**/target/
|
**/target/
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
.env
|
.env
|
||||||
|
/debug_logs/
|
||||||
|
/data/
|
||||||
|
|||||||
210
AGENTS.md
Normal file
210
AGENTS.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
# Banks2FF Development Guide
|
||||||
|
|
||||||
|
## Project Purpose
|
||||||
|
|
||||||
|
Banks2FF is a Rust CLI tool that synchronizes bank transactions from GoCardless Bank Account Data API to Firefly III personal finance manager. It implements a hexagonal architecture for clean separation of concerns and comprehensive testing.
|
||||||
|
|
||||||
|
## 🚨 CRITICAL: Financial Data Security
|
||||||
|
|
||||||
|
### **Financial Data Masking Requirements**
|
||||||
|
|
||||||
|
**FOR LLM/AI INTERACTIONS ONLY**: When interacting with coding agents, LLMs, or AI assistants:
|
||||||
|
- **NEVER** expose, log, or display raw financial information including:
|
||||||
|
- Transaction amounts
|
||||||
|
- Account balances
|
||||||
|
- IBANs or account numbers
|
||||||
|
- Transaction descriptions
|
||||||
|
- Personal identifiers
|
||||||
|
- API keys or tokens
|
||||||
|
|
||||||
|
**FOR DEBUG LOGGING**: When using `RUST_LOG=debug`:
|
||||||
|
- **STRUCTURED LOGGING** shows HTTP requests, responses, and errors
|
||||||
|
- **NO SENSITIVE DATA** is logged (financial amounts, personal info, tokens)
|
||||||
|
- **REQUEST TRACING** includes method, URL, status codes, and error details
|
||||||
|
|
||||||
|
### **Compliance Protocol for AI Agent Debugging**
|
||||||
|
|
||||||
|
When debugging financial data issues with AI agents:
|
||||||
|
|
||||||
|
1. **Create Anonymized Test Scripts**: Write small, focused scripts that extract only the necessary data structure information
|
||||||
|
2. **Use Mock Data**: Replace real financial values with placeholder data
|
||||||
|
3. **Validate Structure, Not Values**: Focus on data structure integrity, not actual financial content
|
||||||
|
4. **Sanitize All Outputs**: Ensure any debugging output masks sensitive information
|
||||||
|
|
||||||
|
### **Debug Logging**
|
||||||
|
|
||||||
|
The application uses structured logging with the `tracing` crate:
|
||||||
|
|
||||||
|
- **Normal operation**: Uses INFO level logging for key operations
|
||||||
|
- **Debug mode**: Set `RUST_LOG=debug` to see detailed HTTP request/response logging
|
||||||
|
- **No sensitive data**: Financial amounts and personal information are never logged
|
||||||
|
- **Request tracing**: HTTP method, URL, status codes, and error responses are logged
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// ✅ GOOD: Structure validation with mock data
|
||||||
|
fn validate_transaction_structure() {
|
||||||
|
let mock_tx = BankTransaction {
|
||||||
|
amount: Decimal::new(12345, 2), // Mock amount
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
// ... other fields with mock data
|
||||||
|
};
|
||||||
|
// Validate structure only
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ BAD: Exposing real financial data
|
||||||
|
fn debug_real_transactions(transactions: Vec<BankTransaction>) {
|
||||||
|
for tx in transactions {
|
||||||
|
println!("Real amount: {}", tx.amount); // SECURITY VIOLATION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rust Development Best Practices
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- **Use `thiserror`** for domain-specific error types in core modules
|
||||||
|
- **Use `anyhow`** for application-level error context and propagation
|
||||||
|
- **Never use `panic!`** in production code - handle errors gracefully
|
||||||
|
- **Implement `From` traits** for error type conversions
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Core domain errors
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum SyncError {
|
||||||
|
#[error("Failed to fetch transactions from source: {0}")]
|
||||||
|
SourceError(#[from] anyhow::Error),
|
||||||
|
#[error("Failed to store transaction: {0}")]
|
||||||
|
DestinationError(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Programming
|
||||||
|
|
||||||
|
- **Use `tokio`** as the async runtime (workspace dependency)
|
||||||
|
- **Prefer `async-trait`** for trait methods that need to be async
|
||||||
|
- **Handle cancellation** properly with `select!` or `tokio::time::timeout`
|
||||||
|
- **Use `?` operator** for error propagation in async functions
|
||||||
|
|
||||||
|
### Testing Strategy
|
||||||
|
|
||||||
|
- **Unit Tests**: Test pure functions and business logic in isolation
|
||||||
|
- **Integration Tests**: Test adapter implementations with `wiremock`
|
||||||
|
- **Mock External Dependencies**: Use `mockall` for trait-based testing
|
||||||
|
- **Test Fixtures**: Store sample JSON responses in `tests/fixtures/`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use mockall::predicate::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_sync_with_mock_source() {
|
||||||
|
let mut mock_source = MockTransactionSource::new();
|
||||||
|
// Setup mock expectations
|
||||||
|
// Test core logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Organization
|
||||||
|
|
||||||
|
- **Workspace Dependencies**: Define common dependencies in root `Cargo.toml`
|
||||||
|
- **Feature Flags**: Use features for optional functionality
|
||||||
|
- **Module Structure**: Keep modules focused and single-responsibility
|
||||||
|
- **Public API**: Minimize public surface area; prefer internal modules
|
||||||
|
|
||||||
|
### Dependencies and Patterns
|
||||||
|
|
||||||
|
**Key Workspace Dependencies:**
|
||||||
|
- `tokio`: Async runtime with full features
|
||||||
|
- `reqwest`: HTTP client with JSON support
|
||||||
|
- `serde`/`serde_json`: Serialization/deserialization
|
||||||
|
- `chrono`: Date/time handling with serde support
|
||||||
|
- `rust_decimal`: Precise decimal arithmetic for financial data
|
||||||
|
- `tracing`/`tracing-subscriber`: Structured logging
|
||||||
|
- `clap`: CLI argument parsing with derive macros
|
||||||
|
- `anyhow`/`thiserror`: Error handling
|
||||||
|
- `async-trait`: Async trait support
|
||||||
|
- `wiremock`: HTTP mocking for tests
|
||||||
|
- `mockall`: Runtime mocking for tests
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### 1. Code Development
|
||||||
|
- Write code in appropriate modules following the hexagonal architecture
|
||||||
|
- Keep core business logic separate from external integrations
|
||||||
|
- Use workspace dependencies consistently
|
||||||
|
|
||||||
|
### 2. Testing
|
||||||
|
- Write tests alongside code in `#[cfg(test)]` modules
|
||||||
|
- Test both happy path and error conditions
|
||||||
|
- Use mock objects for external dependencies
|
||||||
|
- Ensure all tests pass: `cargo test --workspace`
|
||||||
|
|
||||||
|
### 3. Code Quality
|
||||||
|
- Follow Rust idioms and conventions
|
||||||
|
- Use `cargo fmt` for formatting
|
||||||
|
- Use `cargo clippy` for linting
|
||||||
|
- Ensure documentation for public APIs
|
||||||
|
|
||||||
|
### 4. Commit Standards
|
||||||
|
- Commit both code and tests together
|
||||||
|
- Write clear, descriptive commit messages
|
||||||
|
- Ensure the workspace compiles: `cargo build --workspace`
|
||||||
|
|
||||||
|
### Version Control
|
||||||
|
- **Use JJ (Jujutsu)** as the primary tool for all source control operations due to its concurrency and conflict-free design
|
||||||
|
- **Git fallback**: Only for complex operations unsupported by JJ (e.g., interactive rebasing)
|
||||||
|
|
||||||
|
## Project Structure Guidelines
|
||||||
|
|
||||||
|
### Core Module (`banks2ff/src/core/`)
|
||||||
|
- **models.rs**: Domain entities (BankTransaction, Account)
|
||||||
|
- **ports.rs**: Trait definitions (TransactionSource, TransactionDestination)
|
||||||
|
- **sync.rs**: Business logic orchestration
|
||||||
|
|
||||||
|
### Adapters Module (`banks2ff/src/adapters/`)
|
||||||
|
- **gocardless/**: GoCardless API integration
|
||||||
|
- **firefly/**: Firefly III API integration
|
||||||
|
- Each adapter implements the appropriate port trait
|
||||||
|
|
||||||
|
### Client Libraries
|
||||||
|
- **gocardless-client/**: Standalone GoCardless API wrapper
|
||||||
|
- **firefly-client/**: Standalone Firefly III API wrapper
|
||||||
|
- Both use `reqwest` for HTTP communication
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- **Never log sensitive data**: Use tracing filters to exclude financial information
|
||||||
|
- **Environment variables**: Store credentials in `.env` file (never in code)
|
||||||
|
- **Input validation**: Validate all external data before processing
|
||||||
|
- **Error messages**: Don't expose sensitive information in error messages
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- **Caching**: Use caching to reduce API calls (see GoCardlessAdapter)
|
||||||
|
- **Rate Limiting**: Handle 429 responses gracefully
|
||||||
|
- **Batch Processing**: Process transactions in reasonable batches
|
||||||
|
- **Async Concurrency**: Use `tokio` for concurrent operations where appropriate
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
- **Structured Logging**: Use `tracing` with spans for operations
|
||||||
|
- **Error Context**: Provide context in error messages for debugging
|
||||||
|
- **Metrics**: Consider adding metrics for sync operations
|
||||||
|
- **Log Levels**: Use appropriate log levels (debug, info, warn, error)
|
||||||
|
|
||||||
|
## Documentation Guidelines
|
||||||
|
|
||||||
|
### README.md
|
||||||
|
- **Keep High-Level**: Focus on user benefits and key features, not technical implementation details
|
||||||
|
- **User-Centric**: Describe what the tool does and why users would want it
|
||||||
|
- **Skip Implementation Details**: Avoid technical jargon, architecture specifics, or internal implementation that users don't need to know
|
||||||
|
- **Feature Descriptions**: Use concise, benefit-focused language (e.g., "Robust Error Handling" rather than "Implements EUA expiry detection with multiple requisition fallback")
|
||||||
|
|
||||||
|
### Technical Documentation
|
||||||
|
- **docs/architecture.md**: Detailed technical specifications, implementation details, and developer-focused content
|
||||||
|
- **specs/**: Implementation planning, API specifications, and historical context
|
||||||
|
- **Code Comments**: Use for implementation details and complex logic explanations
|
||||||
2505
Cargo.lock
generated
2505
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
45
Cargo.toml
45
Cargo.toml
@@ -1,11 +1,34 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
'bank2ff',
|
"banks2ff",
|
||||||
'firefly-iii-api',
|
"firefly-client",
|
||||||
'gocardless-bankaccount-data-api',
|
"gocardless-client",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.package]
|
||||||
tokio = { version = "1", features = ['full'] }
|
version = "0.1.0"
|
||||||
tokio-macros = "2.4.0"
|
edition = "2021"
|
||||||
|
authors = ["Your Name <your.email@example.com>"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
tokio = { version = "1.34", features = ["full"] }
|
||||||
|
anyhow = "1.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
rust_decimal = { version = "1.33", features = ["serde-float"] }
|
||||||
|
async-trait = "0.1"
|
||||||
|
dotenvy = "0.15"
|
||||||
|
clap = { version = "4.4", features = ["derive", "env"] }
|
||||||
|
reqwest = { version = "0.11", default-features = false, features = ["json", "multipart", "rustls-tls"] }
|
||||||
|
url = "2.5"
|
||||||
|
wiremock = "0.5"
|
||||||
|
tokio-test = "0.4"
|
||||||
|
mockall = "0.11"
|
||||||
|
reqwest-middleware = "0.2"
|
||||||
|
hyper = { version = "0.14", features = ["full"] }
|
||||||
|
bytes = "1.0"
|
||||||
|
|||||||
101
README.md
101
README.md
@@ -1,30 +1,71 @@
|
|||||||
# Bank2FF
|
# Banks2FF
|
||||||
|
|
||||||
Bank2FF is a tool that can retrieve bank transactions from Gocardless and
|
A robust command-line tool to synchronize bank transactions from GoCardless (formerly Nordigen) to Firefly III.
|
||||||
add them to Firefly III.
|
|
||||||
|
## ✨ Key Benefits
|
||||||
It contains autogenerated APIs for both Firefly III and for the
|
|
||||||
Gocardless Bank Account Data API.
|
- **Automatic Transaction Sync**: Keep your Firefly III finances up-to-date with your bank accounts
|
||||||
|
- **Intelligent Caching**: Reduces GoCardless API calls by up to 99% through encrypted local storage
|
||||||
## Usage
|
- **Multi-Currency Support**: Handles international transactions and foreign currencies correctly
|
||||||
|
- **Smart Duplicate Detection**: Avoids double-counting transactions automatically
|
||||||
TBD
|
- **Reliable Operation**: Continues working even when some accounts need attention
|
||||||
|
- **Safe Preview Mode**: Test changes before applying them to your finances
|
||||||
|
- **Rate Limit Aware**: Works within API limits to ensure consistent access
|
||||||
## Generating the API clients
|
|
||||||
|
## 🚀 Quick Start
|
||||||
These API clients are generated with the OpenAPI Generators for Rust.
|
|
||||||
|
### Prerequisites
|
||||||
These need Podman installed, and assume this command is run from the same
|
- Rust (latest stable)
|
||||||
directory where this README.md file is located.
|
- GoCardless Bank Account Data account
|
||||||
|
- Running Firefly III instance
|
||||||
For Gocardless:
|
|
||||||
|
### Setup
|
||||||
`podman run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -g rust -o /local/gocardless-bankaccount-data-api -i 'https://bankaccountdata.gocardless.com/api/v2/swagger.json' --additional-properties=library=reqwest,packageName=gocardless-bankaccount-data-api,packageVersion=2.0.0,supportMiddleware=true,avoidBoxedModels=true`
|
1. Copy environment template: `cp env.example .env`
|
||||||
|
2. Fill in your credentials in `.env`:
|
||||||
|
- `GOCARDLESS_ID`: Your GoCardless Secret ID
|
||||||
For Firefly III:
|
- `GOCARDLESS_KEY`: Your GoCardless Secret Key
|
||||||
|
- `FIREFLY_III_URL`: Your Firefly instance URL
|
||||||
If necessary, change the URL to the definition. If that is a new version, then also change the `packageVersion` parameter.
|
- `FIREFLY_III_API_KEY`: Your Personal Access Token
|
||||||
|
- `BANKS2FF_CACHE_KEY`: Required encryption key for secure transaction caching
|
||||||
`podman run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -g rust -o /local/firefly-iii-api -i 'https://api-docs.firefly-iii.org/firefly-iii-2.1.0-v1.yaml' --additional-properties=library=reqwest,packageName=firefly-iii-api,packageVersion=2.1.0,supportMiddleware=true,avoidBoxedModels=true`
|
|
||||||
|
### Usage
|
||||||
|
```bash
|
||||||
|
# Sync all accounts (automatic date range)
|
||||||
|
cargo run -p banks2ff
|
||||||
|
|
||||||
|
# Preview changes without saving
|
||||||
|
cargo run -p banks2ff -- --dry-run
|
||||||
|
|
||||||
|
# Sync specific date range
|
||||||
|
cargo run -p banks2ff -- --start 2023-01-01 --end 2023-01-31
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 What It Does
|
||||||
|
|
||||||
|
Banks2FF automatically:
|
||||||
|
1. Connects to your bank accounts via GoCardless
|
||||||
|
2. Finds matching accounts in your Firefly III instance
|
||||||
|
3. Downloads new transactions since your last sync
|
||||||
|
4. Adds them to Firefly III (avoiding duplicates)
|
||||||
|
5. Handles errors gracefully - keeps working even if some accounts have issues
|
||||||
|
|
||||||
|
## 🔐 Secure Transaction Caching
|
||||||
|
|
||||||
|
Banks2FF automatically caches your transaction data to make future syncs much faster:
|
||||||
|
|
||||||
|
- **Faster Syncs**: Reuses previously downloaded data instead of re-fetching from the bank
|
||||||
|
- **API Efficiency**: Dramatically reduces the number of calls made to GoCardless
|
||||||
|
- **Secure Storage**: Your financial data is safely encrypted on your local machine
|
||||||
|
- **Automatic Management**: The cache works transparently in the background
|
||||||
|
|
||||||
|
The cache requires `BANKS2FF_CACHE_KEY` to be set in your `.env` file for secure encryption (see `env.example` for key generation instructions).
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
- **Account not syncing?** Check that the IBAN matches between GoCardless and Firefly III
|
||||||
|
- **Missing transactions?** The tool syncs from the last transaction date forward
|
||||||
|
- **Rate limited?** The tool automatically handles API limits and retries appropriately
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*For technical details, see [docs/architecture.md](docs/architecture.md)*
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
FIREFLY_III_URL=
|
|
||||||
FIREFLY_III_API_KEY=
|
|
||||||
FIREFLY_III_CLIENT_ID=
|
|
||||||
|
|
||||||
GOCARDLESS_KEY=
|
|
||||||
GOCARDLESS_ID=
|
|
||||||
2
bank2ff/.gitignore
vendored
2
bank2ff/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
/target
|
|
||||||
../.env
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "bank2ff"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
firefly-iii-api = { path = "../firefly-iii-api", version = "2.1.0" }
|
|
||||||
gocardless-bankaccount-data-api = { path = '../gocardless-bankaccount-data-api', version = "2.0.0" }
|
|
||||||
dotenv = "0.15.0"
|
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
|
||||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"source": "ff3-importer-1.5.2",
|
|
||||||
"created_at": "2024-09-07T10:07:05+02:00",
|
|
||||||
"date": "",
|
|
||||||
"default_account": 1142,
|
|
||||||
"delimiter": "comma",
|
|
||||||
"headers": false,
|
|
||||||
"rules": true,
|
|
||||||
"skip_form": false,
|
|
||||||
"add_import_tag": true,
|
|
||||||
"roles": [],
|
|
||||||
"do_mapping": [],
|
|
||||||
"mapping": [],
|
|
||||||
"duplicate_detection_method": "classic",
|
|
||||||
"ignore_duplicate_lines": false,
|
|
||||||
"unique_column_index": 0,
|
|
||||||
"unique_column_type": "external-id",
|
|
||||||
"flow": "nordigen",
|
|
||||||
"content_type": "unknown",
|
|
||||||
"custom_tag": "",
|
|
||||||
"identifier": "0",
|
|
||||||
"connection": "0",
|
|
||||||
"ignore_spectre_categories": false,
|
|
||||||
"grouped_transaction_handling": "",
|
|
||||||
"use_entire_opposing_address": false,
|
|
||||||
"map_all_data": false,
|
|
||||||
"accounts": {
|
|
||||||
"4cda1369-178c-485b-b3d8-1892afdbfb6c": 1142
|
|
||||||
},
|
|
||||||
"date_range": "range",
|
|
||||||
"date_range_number": 30,
|
|
||||||
"date_range_unit": "d",
|
|
||||||
"date_not_before": "2024-06-29",
|
|
||||||
"date_not_after": "2024-09-06",
|
|
||||||
"nordigen_country": "NL",
|
|
||||||
"nordigen_bank": "ASN_BANK_ASNBNL21",
|
|
||||||
"nordigen_requisitions": {
|
|
||||||
"b2e6fd94-fc45-484c-abc1-5f410a58a220": "c5006758-135b-4770-8715-2a047a426973"
|
|
||||||
},
|
|
||||||
"nordigen_max_days": "90",
|
|
||||||
"conversion": false,
|
|
||||||
"ignore_duplicate_transactions": true
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub(super) struct AppConfiguration {
|
|
||||||
pub(crate) firefly_iii_url: String,
|
|
||||||
pub(crate) firefly_iii_api_key: String,
|
|
||||||
|
|
||||||
pub(crate) go_cardless_key: String,
|
|
||||||
pub(crate) go_cardless_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppConfiguration {
|
|
||||||
pub(super) fn from_env() -> Result<Self, dotenv::Error> {
|
|
||||||
use dotenv::var;
|
|
||||||
|
|
||||||
let firefly_iii_url = var("FIREFLY_III_URL")?;
|
|
||||||
let firefly_iii_api_key = var("FIREFLY_III_API_KEY")?;
|
|
||||||
|
|
||||||
let go_cardless_key = var("GOCARDLESS_KEY")?;
|
|
||||||
let go_cardless_id = var("GOCARDLESS_ID")?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
firefly_iii_url,
|
|
||||||
firefly_iii_api_key,
|
|
||||||
go_cardless_key,
|
|
||||||
go_cardless_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
use firefly_iii_api::apis::configuration::Configuration;
|
|
||||||
use firefly_iii_api::apis::{accounts_api, transactions_api};
|
|
||||||
use firefly_iii_api::models::{AccountRead, TransactionRead};
|
|
||||||
|
|
||||||
pub(super) async fn load_all_transactions(ff: &mut FFTransactions) -> Result<(), ()> {
|
|
||||||
let mut has_more = true;
|
|
||||||
let mut page = None;
|
|
||||||
while has_more {
|
|
||||||
match transactions_api::list_transaction(
|
|
||||||
&ff.config,
|
|
||||||
None,
|
|
||||||
Some(500),
|
|
||||||
page,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(transactions) => {
|
|
||||||
has_more = transactions.links.next.is_some();
|
|
||||||
let pagination = transactions.meta.pagination.clone().unwrap();
|
|
||||||
let next = pagination.current_page.unwrap() + 1;
|
|
||||||
page = Some(next);
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"Page {} of {}",
|
|
||||||
pagination.current_page.unwrap(),
|
|
||||||
pagination.total_pages.unwrap()
|
|
||||||
);
|
|
||||||
transactions
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.data
|
|
||||||
.iter()
|
|
||||||
.for_each(|tx| {
|
|
||||||
ff.transactions.push(tx.to_owned());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn load_all_accounts(ff: &mut FFTransactions) -> Result<(), ()> {
|
|
||||||
let mut has_more = true;
|
|
||||||
let mut page = None;
|
|
||||||
while has_more {
|
|
||||||
match accounts_api::list_account(&ff.config, None, Some(500), page, None, None).await {
|
|
||||||
Ok(accounts) => {
|
|
||||||
let pagination = accounts.meta.pagination.clone().unwrap();
|
|
||||||
has_more = pagination.current_page < pagination.total_pages;
|
|
||||||
|
|
||||||
let next = pagination.current_page.unwrap() + 1;
|
|
||||||
page = Some(next);
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"Page {} of {}",
|
|
||||||
pagination.current_page.unwrap(),
|
|
||||||
pagination.total_pages.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
accounts.data
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.iter()
|
|
||||||
.for_each(|a| ff.accounts.push(a.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct FFTransactions {
|
|
||||||
accounts: Vec<AccountRead>,
|
|
||||||
transactions: Vec<TransactionRead>,
|
|
||||||
config: Configuration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FFTransactions {
|
|
||||||
pub(super) fn new(config: Configuration) -> Self {
|
|
||||||
Self {
|
|
||||||
accounts: Vec::with_capacity(1000),
|
|
||||||
transactions: Vec::with_capacity(10000),
|
|
||||||
config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn accounts(&self) -> &Vec<AccountRead> {
|
|
||||||
&self.accounts
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn transactions(&self) -> &Vec<TransactionRead> {
|
|
||||||
&self.transactions
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_account_by_iban(&self, iban: &str) -> Option<&AccountRead> {
|
|
||||||
let to_check = Some(Some(iban.to_owned()));
|
|
||||||
|
|
||||||
self.accounts.iter().find(|a| a.attributes.iban == to_check)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
mod config;
|
|
||||||
mod firefly;
|
|
||||||
|
|
||||||
use crate::config::AppConfiguration;
|
|
||||||
use crate::firefly::{load_all_accounts, load_all_transactions, FFTransactions};
|
|
||||||
use firefly_iii_api::apis::configuration;
|
|
||||||
use tokio::io::AsyncReadExt;
|
|
||||||
use gocardless_bankaccount_data_api::models::{JwtObtainPairRequest, StatusEnum};
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), dotenv::Error> {
|
|
||||||
dotenv::dotenv().ok();
|
|
||||||
let config = AppConfiguration::from_env()?;
|
|
||||||
|
|
||||||
// let mut ff = FFTransactions::new(configuration::Configuration {
|
|
||||||
// base_path: config.firefly_iii_url,
|
|
||||||
// user_agent: None,
|
|
||||||
// client: Default::default(),
|
|
||||||
// basic_auth: None,
|
|
||||||
// oauth_access_token: None,
|
|
||||||
// bearer_access_token: Some(config.firefly_iii_api_key),
|
|
||||||
// api_key: None,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// let _ = load_all_accounts(&mut ff).await;
|
|
||||||
// println!("#Accounts:\t{}", ff.accounts().len());
|
|
||||||
|
|
||||||
// let _ = load_all_transactions(&mut ff).await;
|
|
||||||
// println!("#Transactions:\t{}", ff.transactions().len());
|
|
||||||
|
|
||||||
let mut gocardless_config = gocardless_bankaccount_data_api::apis::configuration::Configuration::new();
|
|
||||||
let gc_token_pair = gocardless_bankaccount_data_api::apis::token_api::obtain_new_access_slash_refresh_token_pair(
|
|
||||||
&gocardless_config,JwtObtainPairRequest::new(config.go_cardless_id, config.go_cardless_key)).await.unwrap();
|
|
||||||
|
|
||||||
gocardless_config.bearer_access_token = gc_token_pair.access;
|
|
||||||
|
|
||||||
|
|
||||||
// let institutions = gocardless_bankaccount_data_api::apis::institutions_api::retrieve_all_supported_institutions_in_a_given_country(
|
|
||||||
// &gocardless_config,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// Some("NL"),
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None,
|
|
||||||
// None
|
|
||||||
// ).await.unwrap();
|
|
||||||
|
|
||||||
let gc_reqs = gocardless_bankaccount_data_api::apis::requisitions_api::retrieve_all_requisitions(&gocardless_config, None, None)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
dbg!("# of requisitions:{} ", &gc_reqs.results.len());
|
|
||||||
|
|
||||||
|
|
||||||
let active_reqs = gc_reqs
|
|
||||||
.results.iter()
|
|
||||||
.filter(|req| req.status == Some(StatusEnum::Ln) && req.institution_id != "BUNQ_BUNQNL2A").collect::<Vec<_>>();
|
|
||||||
|
|
||||||
|
|
||||||
dbg!("# of active requisitions:{} ", &active_reqs.len());
|
|
||||||
|
|
||||||
for req in active_reqs {
|
|
||||||
if req.accounts.is_none() {
|
|
||||||
dbg!("No active accounts for requisition {}", req.id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let accounts = req.accounts.as_ref().unwrap();
|
|
||||||
|
|
||||||
for account in accounts {
|
|
||||||
let transactions_resp = gocardless_bankaccount_data_api::apis::accounts_api::retrieve_account_transactions(
|
|
||||||
&gocardless_config,
|
|
||||||
&account.to_string(),
|
|
||||||
Some("2024-09-01".to_string()),
|
|
||||||
Some("2024-09-30".to_string())
|
|
||||||
).await;
|
|
||||||
|
|
||||||
if transactions_resp.is_err() {
|
|
||||||
dbg!("{:?}", transactions_resp.unwrap_err());
|
|
||||||
// TODO: Do something smarter here, if possible
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let transactions = transactions_resp.unwrap();
|
|
||||||
dbg!(&transactions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = tokio::io::stdin().read_u8().await;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
42
banks2ff/Cargo.toml
Normal file
42
banks2ff/Cargo.toml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
[package]
|
||||||
|
name = "banks2ff"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
|
rust_decimal = { workspace = true }
|
||||||
|
dotenvy = { workspace = true }
|
||||||
|
clap = { workspace = true }
|
||||||
|
reqwest = { workspace = true }
|
||||||
|
|
||||||
|
# Core logic dependencies
|
||||||
|
async-trait = { workspace = true }
|
||||||
|
|
||||||
|
# API Client dependencies
|
||||||
|
firefly-client = { path = "../firefly-client" }
|
||||||
|
gocardless-client = { path = "../gocardless-client" }
|
||||||
|
|
||||||
|
# Debug logging dependencies
|
||||||
|
reqwest-middleware = { workspace = true }
|
||||||
|
hyper = { workspace = true }
|
||||||
|
bytes = { workspace = true }
|
||||||
|
http = "0.2"
|
||||||
|
task-local-extensions = "0.1"
|
||||||
|
|
||||||
|
# Encryption dependencies
|
||||||
|
aes-gcm = "0.10"
|
||||||
|
pbkdf2 = "0.12"
|
||||||
|
rand = "0.8"
|
||||||
|
sha2 = "0.10"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
mockall = { workspace = true }
|
||||||
188
banks2ff/src/adapters/firefly/client.rs
Normal file
188
banks2ff/src/adapters/firefly/client.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use anyhow::Result;
|
||||||
|
use tracing::instrument;
|
||||||
|
use crate::core::ports::{TransactionDestination, TransactionMatch};
|
||||||
|
use crate::core::models::BankTransaction;
|
||||||
|
use firefly_client::client::FireflyClient;
|
||||||
|
use firefly_client::models::{TransactionStore, TransactionSplitStore, TransactionUpdate, TransactionSplitUpdate};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
|
pub struct FireflyAdapter {
|
||||||
|
client: Arc<Mutex<FireflyClient>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FireflyAdapter {
|
||||||
|
pub fn new(client: FireflyClient) -> Self {
|
||||||
|
Self {
|
||||||
|
client: Arc::new(Mutex::new(client)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TransactionDestination for FireflyAdapter {
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn resolve_account_id(&self, iban: &str) -> Result<Option<String>> {
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
let accounts = client.search_accounts(iban).await?;
|
||||||
|
|
||||||
|
// Look for exact match on IBAN, ensuring account is active
|
||||||
|
for acc in accounts.data {
|
||||||
|
// Filter for active accounts only (default is usually active, but let's check if attribute exists)
|
||||||
|
// Note: The Firefly API spec v6.4.4 Account object has 'active' attribute as boolean.
|
||||||
|
let is_active = acc.attributes.active.unwrap_or(true);
|
||||||
|
|
||||||
|
if !is_active {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(acc_iban) = acc.attributes.iban {
|
||||||
|
if acc_iban.replace(" ", "") == iban.replace(" ", "") {
|
||||||
|
return Ok(Some(acc.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn get_active_account_ibans(&self) -> Result<Vec<String>> {
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
// Get all asset accounts. Note: Pagination might be needed if user has > 50 accounts.
|
||||||
|
// For typical users, 50 is enough. If needed we can loop pages.
|
||||||
|
// The client `get_accounts` method hardcodes limit=default. We should probably expose a list_all method or loop here.
|
||||||
|
// For now, let's assume page 1 covers it or use search.
|
||||||
|
|
||||||
|
let accounts = client.get_accounts("").await?; // Argument ignored in current impl
|
||||||
|
let mut ibans = Vec::new();
|
||||||
|
|
||||||
|
for acc in accounts.data {
|
||||||
|
let is_active = acc.attributes.active.unwrap_or(true);
|
||||||
|
if is_active {
|
||||||
|
if let Some(iban) = acc.attributes.iban {
|
||||||
|
if !iban.is_empty() {
|
||||||
|
ibans.push(iban);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ibans)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn get_last_transaction_date(&self, account_id: &str) -> Result<Option<NaiveDate>> {
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
// Fetch latest 1 transaction
|
||||||
|
let tx_list = client.list_account_transactions(account_id, None, None).await?;
|
||||||
|
|
||||||
|
if let Some(first) = tx_list.data.first() {
|
||||||
|
if let Some(split) = first.attributes.transactions.first() {
|
||||||
|
// Format is usually YYYY-MM-DDT... or YYYY-MM-DD
|
||||||
|
let date_str = split.date.split('T').next().unwrap_or(&split.date);
|
||||||
|
if let Ok(date) = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
|
||||||
|
return Ok(Some(date));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn find_transaction(&self, account_id: &str, tx: &BankTransaction) -> Result<Option<TransactionMatch>> {
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
|
||||||
|
// Search window: +/- 3 days
|
||||||
|
let start_date = tx.date - chrono::Duration::days(3);
|
||||||
|
let end_date = tx.date + chrono::Duration::days(3);
|
||||||
|
|
||||||
|
let tx_list = client.list_account_transactions(
|
||||||
|
account_id,
|
||||||
|
Some(&start_date.format("%Y-%m-%d").to_string()),
|
||||||
|
Some(&end_date.format("%Y-%m-%d").to_string())
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
// Filter logic
|
||||||
|
for existing_tx in tx_list.data {
|
||||||
|
for split in existing_tx.attributes.transactions {
|
||||||
|
// 1. Check Amount (exact match absolute value)
|
||||||
|
if let Ok(amount) = Decimal::from_str(&split.amount) {
|
||||||
|
if amount.abs() == tx.amount.abs() {
|
||||||
|
// 2. Check External ID
|
||||||
|
if let Some(ref ext_id) = split.external_id {
|
||||||
|
if ext_id == &tx.internal_id {
|
||||||
|
return Ok(Some(TransactionMatch {
|
||||||
|
id: existing_tx.id.clone(),
|
||||||
|
has_external_id: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 3. "Naked" transaction match (Heuristic)
|
||||||
|
// If currency matches
|
||||||
|
if let Some(ref code) = split.currency_code {
|
||||||
|
if code != &tx.currency {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Some(TransactionMatch {
|
||||||
|
id: existing_tx.id.clone(),
|
||||||
|
has_external_id: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn create_transaction(&self, account_id: &str, tx: &BankTransaction) -> Result<()> {
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
|
||||||
|
// Map to Firefly Transaction
|
||||||
|
let is_credit = tx.amount.is_sign_positive();
|
||||||
|
let transaction_type = if is_credit { "deposit" } else { "withdrawal" };
|
||||||
|
|
||||||
|
let split = TransactionSplitStore {
|
||||||
|
transaction_type: transaction_type.to_string(),
|
||||||
|
date: tx.date.format("%Y-%m-%d").to_string(),
|
||||||
|
amount: tx.amount.abs().to_string(),
|
||||||
|
description: tx.description.clone(),
|
||||||
|
source_id: if !is_credit { Some(account_id.to_string()) } else { None },
|
||||||
|
source_name: if is_credit { tx.counterparty_name.clone().or(Some("Unknown Sender".to_string())) } else { None },
|
||||||
|
destination_id: if is_credit { Some(account_id.to_string()) } else { None },
|
||||||
|
destination_name: if !is_credit { tx.counterparty_name.clone().or(Some("Unknown Recipient".to_string())) } else { None },
|
||||||
|
currency_code: Some(tx.currency.clone()),
|
||||||
|
foreign_amount: tx.foreign_amount.map(|d| d.abs().to_string()),
|
||||||
|
foreign_currency_code: tx.foreign_currency.clone(),
|
||||||
|
external_id: Some(tx.internal_id.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let store = TransactionStore {
|
||||||
|
transactions: vec![split],
|
||||||
|
apply_rules: Some(true),
|
||||||
|
fire_webhooks: Some(true),
|
||||||
|
error_if_duplicate_hash: Some(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.store_transaction(store).await.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn update_transaction_external_id(&self, id: &str, external_id: &str) -> Result<()> {
|
||||||
|
let client = self.client.lock().await;
|
||||||
|
let update = TransactionUpdate {
|
||||||
|
transactions: vec![TransactionSplitUpdate {
|
||||||
|
external_id: Some(external_id.to_string()),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
client.update_transaction(id, update).await.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
1
banks2ff/src/adapters/firefly/mod.rs
Normal file
1
banks2ff/src/adapters/firefly/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod client;
|
||||||
59
banks2ff/src/adapters/gocardless/cache.rs
Normal file
59
banks2ff/src/adapters/gocardless/cache.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::warn;
|
||||||
|
use crate::adapters::gocardless::encryption::Encryption;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct AccountCache {
|
||||||
|
/// Map of Account ID -> IBAN
|
||||||
|
pub accounts: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountCache {
|
||||||
|
fn get_path() -> String {
|
||||||
|
let cache_dir = std::env::var("BANKS2FF_CACHE_DIR").unwrap_or_else(|_| "data/cache".to_string());
|
||||||
|
format!("{}/accounts.enc", cache_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load() -> Self {
|
||||||
|
let path = Self::get_path();
|
||||||
|
if Path::new(&path).exists() {
|
||||||
|
match fs::read(&path) {
|
||||||
|
Ok(encrypted_data) => match Encryption::decrypt(&encrypted_data) {
|
||||||
|
Ok(json_data) => match serde_json::from_slice(&json_data) {
|
||||||
|
Ok(cache) => return cache,
|
||||||
|
Err(e) => warn!("Failed to parse cache file: {}", e),
|
||||||
|
},
|
||||||
|
Err(e) => warn!("Failed to decrypt cache file: {}", e),
|
||||||
|
},
|
||||||
|
Err(e) => warn!("Failed to read cache file: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self) {
|
||||||
|
let path = Self::get_path();
|
||||||
|
match serde_json::to_vec(self) {
|
||||||
|
Ok(json_data) => match Encryption::encrypt(&json_data) {
|
||||||
|
Ok(encrypted_data) => {
|
||||||
|
if let Err(e) = fs::write(&path, encrypted_data) {
|
||||||
|
warn!("Failed to write cache file: {}", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => warn!("Failed to encrypt cache: {}", e),
|
||||||
|
},
|
||||||
|
Err(e) => warn!("Failed to serialize cache: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_iban(&self, account_id: &str) -> Option<String> {
|
||||||
|
self.accounts.get(account_id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, account_id: String, iban: String) {
|
||||||
|
self.accounts.insert(account_id, iban);
|
||||||
|
}
|
||||||
|
}
|
||||||
201
banks2ff/src/adapters/gocardless/client.rs
Normal file
201
banks2ff/src/adapters/gocardless/client.rs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use anyhow::Result;
|
||||||
|
use tracing::{info, instrument, warn};
|
||||||
|
use crate::core::ports::TransactionSource;
|
||||||
|
use crate::core::models::{Account, BankTransaction};
|
||||||
|
use crate::adapters::gocardless::mapper::map_transaction;
|
||||||
|
use crate::adapters::gocardless::cache::AccountCache;
|
||||||
|
use crate::adapters::gocardless::transaction_cache::AccountTransactionCache;
|
||||||
|
use gocardless_client::client::GoCardlessClient;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
pub struct GoCardlessAdapter {
|
||||||
|
client: Arc<Mutex<GoCardlessClient>>,
|
||||||
|
cache: Arc<Mutex<AccountCache>>,
|
||||||
|
transaction_caches: Arc<Mutex<HashMap<String, AccountTransactionCache>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GoCardlessAdapter {
|
||||||
|
pub fn new(client: GoCardlessClient) -> Self {
|
||||||
|
Self {
|
||||||
|
client: Arc::new(Mutex::new(client)),
|
||||||
|
cache: Arc::new(Mutex::new(AccountCache::load())),
|
||||||
|
transaction_caches: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl TransactionSource for GoCardlessAdapter {
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn get_accounts(&self, wanted_ibans: Option<Vec<String>>) -> Result<Vec<Account>> {
|
||||||
|
let mut client = self.client.lock().await;
|
||||||
|
let mut cache = self.cache.lock().await;
|
||||||
|
|
||||||
|
// Ensure token
|
||||||
|
client.obtain_access_token().await?;
|
||||||
|
|
||||||
|
let requisitions = client.get_requisitions().await?;
|
||||||
|
let mut accounts = Vec::new();
|
||||||
|
|
||||||
|
// Build a hashset of wanted IBANs if provided, for faster lookup
|
||||||
|
let wanted_set = wanted_ibans.map(|list| {
|
||||||
|
list.into_iter()
|
||||||
|
.map(|i| i.replace(" ", ""))
|
||||||
|
.collect::<std::collections::HashSet<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut found_count = 0;
|
||||||
|
let target_count = wanted_set.as_ref().map(|s| s.len()).unwrap_or(0);
|
||||||
|
|
||||||
|
for req in requisitions.results {
|
||||||
|
// Optimization: Only process Linked requisitions to avoid 401/403 on expired ones
|
||||||
|
if req.status != "LN" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if agreement is expired
|
||||||
|
if let Some(agreement_id) = &req.agreement {
|
||||||
|
match client.is_agreement_expired(agreement_id).await {
|
||||||
|
Ok(true) => {
|
||||||
|
warn!("Skipping requisition {} - agreement {} has expired", req.id, agreement_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
// Agreement is valid, proceed
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to check agreement {} expiry: {}. Skipping requisition.", agreement_id, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(req_accounts) = req.accounts {
|
||||||
|
for acc_id in req_accounts {
|
||||||
|
// 1. Check Cache
|
||||||
|
let mut iban_opt = cache.get_iban(&acc_id);
|
||||||
|
|
||||||
|
// 2. Fetch if missing
|
||||||
|
if iban_opt.is_none() {
|
||||||
|
match client.get_account(&acc_id).await {
|
||||||
|
Ok(details) => {
|
||||||
|
let new_iban = details.iban.unwrap_or_default();
|
||||||
|
cache.insert(acc_id.clone(), new_iban.clone());
|
||||||
|
cache.save();
|
||||||
|
iban_opt = Some(new_iban);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// If rate limit hit here, we might want to skip this account and continue?
|
||||||
|
// But get_account is critical to identify the account.
|
||||||
|
// If we fail here, we can't match.
|
||||||
|
warn!("Failed to fetch details for account {}: {}", acc_id, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let iban = iban_opt.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut keep = true;
|
||||||
|
if let Some(ref wanted) = wanted_set {
|
||||||
|
if !wanted.contains(&iban.replace(" ", "")) {
|
||||||
|
keep = false;
|
||||||
|
} else {
|
||||||
|
found_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if keep {
|
||||||
|
accounts.push(Account {
|
||||||
|
id: acc_id,
|
||||||
|
iban,
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimization: Stop if we found all wanted accounts
|
||||||
|
if let Some(_) = wanted_set {
|
||||||
|
if found_count >= target_count && target_count > 0 {
|
||||||
|
info!("Found all {} wanted accounts. Stopping search.", target_count);
|
||||||
|
return Ok(accounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Found {} matching accounts in GoCardless", accounts.len());
|
||||||
|
Ok(accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
async fn get_transactions(&self, account_id: &str, start: NaiveDate, end: NaiveDate) -> Result<Vec<BankTransaction>> {
|
||||||
|
let mut client = self.client.lock().await;
|
||||||
|
client.obtain_access_token().await?;
|
||||||
|
|
||||||
|
// Load or get transaction cache
|
||||||
|
let mut caches = self.transaction_caches.lock().await;
|
||||||
|
let cache = caches.entry(account_id.to_string()).or_insert_with(|| {
|
||||||
|
AccountTransactionCache::load(account_id).unwrap_or_else(|_| AccountTransactionCache {
|
||||||
|
account_id: account_id.to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get cached transactions
|
||||||
|
let mut raw_transactions = cache.get_cached_transactions(start, end);
|
||||||
|
|
||||||
|
// Get uncovered ranges
|
||||||
|
let uncovered_ranges = cache.get_uncovered_ranges(start, end);
|
||||||
|
|
||||||
|
// Fetch missing ranges
|
||||||
|
for (range_start, range_end) in uncovered_ranges {
|
||||||
|
let response_result = client.get_transactions(
|
||||||
|
account_id,
|
||||||
|
Some(&range_start.to_string()),
|
||||||
|
Some(&range_end.to_string())
|
||||||
|
).await;
|
||||||
|
|
||||||
|
match response_result {
|
||||||
|
Ok(response) => {
|
||||||
|
let raw_txs = response.transactions.booked.clone();
|
||||||
|
raw_transactions.extend(raw_txs.clone());
|
||||||
|
cache.store_transactions(range_start, range_end, raw_txs);
|
||||||
|
info!("Fetched {} transactions for account {} in range {}-{}", response.transactions.booked.len(), account_id, range_start, range_end);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
let err_str = e.to_string();
|
||||||
|
if err_str.contains("429") {
|
||||||
|
warn!("Rate limit reached for account {} in range {}-{}. Skipping.", account_id, range_start, range_end);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if err_str.contains("401") && (err_str.contains("expired") || err_str.contains("EUA")) {
|
||||||
|
warn!("EUA expired for account {} in range {}-{}. Skipping.", account_id, range_start, range_end);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save cache
|
||||||
|
cache.save()?;
|
||||||
|
|
||||||
|
// Map to BankTransaction
|
||||||
|
let mut transactions = Vec::new();
|
||||||
|
for tx in raw_transactions {
|
||||||
|
match map_transaction(tx) {
|
||||||
|
Ok(t) => transactions.push(t),
|
||||||
|
Err(e) => tracing::error!("Failed to map transaction: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Total {} transactions for account {} in range {}-{}", transactions.len(), account_id, start, end);
|
||||||
|
Ok(transactions)
|
||||||
|
}
|
||||||
|
}
|
||||||
173
banks2ff/src/adapters/gocardless/encryption.rs
Normal file
173
banks2ff/src/adapters/gocardless/encryption.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
//! # Encryption Module
|
||||||
|
//!
|
||||||
|
//! Provides AES-GCM encryption for sensitive cache data using PBKDF2 key derivation.
|
||||||
|
//!
|
||||||
|
//! ## Security Considerations
|
||||||
|
//!
|
||||||
|
//! - **Algorithm**: AES-GCM (Authenticated Encryption) with 256-bit keys
|
||||||
|
//! - **Key Derivation**: PBKDF2 with 200,000 iterations for brute-force resistance
|
||||||
|
//! - **Salt**: Random 16-byte salt per encryption (prepended to ciphertext)
|
||||||
|
//! - **Nonce**: Random 96-bit nonce per encryption (prepended to ciphertext)
|
||||||
|
//! - **Key Source**: Environment variable `BANKS2FF_CACHE_KEY`
|
||||||
|
//!
|
||||||
|
//! ## Data Format
|
||||||
|
//!
|
||||||
|
//! Encrypted data format: `[salt(16)][nonce(12)][ciphertext]`
|
||||||
|
//!
|
||||||
|
//! ## Security Guarantees
|
||||||
|
//!
|
||||||
|
//! - **Confidentiality**: AES-GCM encryption protects data at rest
|
||||||
|
//! - **Integrity**: GCM authentication prevents tampering
|
||||||
|
//! - **Forward Security**: Unique salt/nonce per encryption prevents rainbow tables
|
||||||
|
//! - **Key Security**: PBKDF2 slows brute-force attacks
|
||||||
|
//!
|
||||||
|
//! ## Performance
|
||||||
|
//!
|
||||||
|
//! - Encryption: ~10-50μs for typical cache payloads
|
||||||
|
//! - Key derivation: ~50-100ms (computed once per operation)
|
||||||
|
//! - Memory: Minimal additional overhead
|
||||||
|
|
||||||
|
use aes_gcm::{Aes256Gcm, Key, Nonce};
|
||||||
|
use aes_gcm::aead::{Aead, KeyInit};
|
||||||
|
use pbkdf2::pbkdf2_hmac;
|
||||||
|
use rand::RngCore;
|
||||||
|
use sha2::Sha256;
|
||||||
|
use std::env;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
|
const KEY_LEN: usize = 32; // 256-bit key
|
||||||
|
const NONCE_LEN: usize = 12; // 96-bit nonce for AES-GCM
|
||||||
|
const SALT_LEN: usize = 16; // 128-bit salt for PBKDF2
|
||||||
|
|
||||||
|
pub struct Encryption;
|
||||||
|
|
||||||
|
impl Encryption {
|
||||||
|
/// Derive encryption key from environment variable and salt
|
||||||
|
pub fn derive_key(password: &str, salt: &[u8]) -> Key<Aes256Gcm> {
|
||||||
|
let mut key = [0u8; KEY_LEN];
|
||||||
|
pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, 200_000, &mut key);
|
||||||
|
key.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get password from environment variable
|
||||||
|
fn get_password() -> Result<String> {
|
||||||
|
env::var("BANKS2FF_CACHE_KEY")
|
||||||
|
.map_err(|_| anyhow!("BANKS2FF_CACHE_KEY environment variable not set"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt data using AES-GCM
|
||||||
|
pub fn encrypt(data: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let password = Self::get_password()?;
|
||||||
|
|
||||||
|
// Generate random salt
|
||||||
|
let mut salt = [0u8; SALT_LEN];
|
||||||
|
rand::thread_rng().fill_bytes(&mut salt);
|
||||||
|
|
||||||
|
let key = Self::derive_key(&password, &salt);
|
||||||
|
let cipher = Aes256Gcm::new(&key);
|
||||||
|
|
||||||
|
// Generate random nonce
|
||||||
|
let mut nonce_bytes = [0u8; NONCE_LEN];
|
||||||
|
rand::thread_rng().fill_bytes(&mut nonce_bytes);
|
||||||
|
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
let ciphertext = cipher.encrypt(nonce, data)
|
||||||
|
.map_err(|e| anyhow!("Encryption failed: {}", e))?;
|
||||||
|
|
||||||
|
// Prepend salt and nonce to ciphertext: [salt(16)][nonce(12)][ciphertext]
|
||||||
|
let mut result = salt.to_vec();
|
||||||
|
result.extend(nonce_bytes);
|
||||||
|
result.extend(ciphertext);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt data using AES-GCM
|
||||||
|
pub fn decrypt(encrypted_data: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let min_len = SALT_LEN + NONCE_LEN;
|
||||||
|
if encrypted_data.len() < min_len {
|
||||||
|
return Err(anyhow!("Encrypted data too short"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = Self::get_password()?;
|
||||||
|
|
||||||
|
// Extract salt, nonce and ciphertext: [salt(16)][nonce(12)][ciphertext]
|
||||||
|
let salt = &encrypted_data[..SALT_LEN];
|
||||||
|
let nonce = Nonce::from_slice(&encrypted_data[SALT_LEN..min_len]);
|
||||||
|
let ciphertext = &encrypted_data[min_len..];
|
||||||
|
|
||||||
|
let key = Self::derive_key(&password, salt);
|
||||||
|
let cipher = Aes256Gcm::new(&key);
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
cipher.decrypt(nonce, ciphertext)
|
||||||
|
.map_err(|e| anyhow!("Decryption failed: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encrypt_decrypt_round_trip() {
|
||||||
|
// Set test environment variable
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-key-for-encryption");
|
||||||
|
|
||||||
|
let original_data = b"Hello, World! This is test data.";
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
let encrypted = Encryption::encrypt(original_data).expect("Encryption should succeed");
|
||||||
|
|
||||||
|
// Ensure env var is still set for decryption
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-key-for-encryption");
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
let decrypted = Encryption::decrypt(&encrypted).expect("Decryption should succeed");
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assert_eq!(original_data.to_vec(), decrypted);
|
||||||
|
assert_ne!(original_data.to_vec(), encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encrypt_decrypt_different_keys() {
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "key1");
|
||||||
|
let data = b"Test data";
|
||||||
|
let encrypted = Encryption::encrypt(data).unwrap();
|
||||||
|
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "key2");
|
||||||
|
let result = Encryption::decrypt(&encrypted);
|
||||||
|
assert!(result.is_err(), "Should fail with different key");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_env_var() {
|
||||||
|
// Save current value and restore after test
|
||||||
|
let original_value = env::var("BANKS2FF_CACHE_KEY").ok();
|
||||||
|
env::remove_var("BANKS2FF_CACHE_KEY");
|
||||||
|
|
||||||
|
let result = Encryption::get_password();
|
||||||
|
assert!(result.is_err(), "Should fail without env var");
|
||||||
|
|
||||||
|
// Restore original value
|
||||||
|
if let Some(val) = original_value {
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_small_data() {
|
||||||
|
// Set env var multiple times to ensure it's available
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-key");
|
||||||
|
let data = b"{}"; // Minimal JSON object
|
||||||
|
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-key");
|
||||||
|
let encrypted = Encryption::encrypt(data).unwrap();
|
||||||
|
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-key");
|
||||||
|
let decrypted = Encryption::decrypt(&encrypted).unwrap();
|
||||||
|
assert_eq!(data.to_vec(), decrypted);
|
||||||
|
}
|
||||||
|
}
|
||||||
281
banks2ff/src/adapters/gocardless/mapper.rs
Normal file
281
banks2ff/src/adapters/gocardless/mapper.rs
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
use rust_decimal::Decimal;
|
||||||
|
use rust_decimal::prelude::Signed;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use anyhow::Result;
|
||||||
|
use crate::core::models::BankTransaction;
|
||||||
|
use gocardless_client::models::Transaction;
|
||||||
|
|
||||||
|
pub fn map_transaction(tx: Transaction) -> Result<BankTransaction> {
|
||||||
|
let internal_id = tx.transaction_id
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Transaction ID missing"))?;
|
||||||
|
|
||||||
|
let date_str = tx.booking_date.or(tx.value_date)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Transaction date missing"))?;
|
||||||
|
let date = chrono::NaiveDate::parse_from_str(&date_str, "%Y-%m-%d")?;
|
||||||
|
|
||||||
|
let amount = Decimal::from_str(&tx.transaction_amount.amount)?;
|
||||||
|
validate_amount(&amount)?;
|
||||||
|
let currency = tx.transaction_amount.currency;
|
||||||
|
validate_currency(¤cy)?;
|
||||||
|
|
||||||
|
let mut foreign_amount = None;
|
||||||
|
let mut foreign_currency = None;
|
||||||
|
|
||||||
|
if let Some(exchanges) = tx.currency_exchange {
|
||||||
|
if let Some(exchange) = exchanges.first() {
|
||||||
|
if let (Some(source_curr), Some(rate_str)) = (&exchange.source_currency, &exchange.exchange_rate) {
|
||||||
|
foreign_currency = Some(source_curr.clone());
|
||||||
|
if let Ok(rate) = Decimal::from_str(rate_str) {
|
||||||
|
let calc = amount.abs() * rate;
|
||||||
|
let sign = amount.signum();
|
||||||
|
foreign_amount = Some(calc * sign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref fa) = foreign_amount {
|
||||||
|
validate_amount(fa)?;
|
||||||
|
}
|
||||||
|
if let Some(ref fc) = foreign_currency {
|
||||||
|
validate_currency(fc)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for description: Remittance Unstructured -> Debtor/Creditor Name -> "Unknown"
|
||||||
|
let description = tx.remittance_information_unstructured
|
||||||
|
.or(tx.creditor_name.clone())
|
||||||
|
.or(tx.debtor_name.clone())
|
||||||
|
.unwrap_or_else(|| "Unknown Transaction".to_string());
|
||||||
|
|
||||||
|
Ok(BankTransaction {
|
||||||
|
internal_id,
|
||||||
|
date,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
foreign_amount,
|
||||||
|
foreign_currency,
|
||||||
|
description,
|
||||||
|
counterparty_name: tx.creditor_name.or(tx.debtor_name),
|
||||||
|
counterparty_iban: tx.creditor_account.and_then(|a| a.iban).or(tx.debtor_account.and_then(|a| a.iban)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_amount(amount: &Decimal) -> Result<()> {
|
||||||
|
let abs = amount.abs();
|
||||||
|
if abs > Decimal::new(1_000_000_000, 0) {
|
||||||
|
return Err(anyhow::anyhow!("Amount exceeds reasonable bounds: {}", amount));
|
||||||
|
}
|
||||||
|
if abs == Decimal::ZERO {
|
||||||
|
return Err(anyhow::anyhow!("Amount cannot be zero"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_currency(currency: &str) -> Result<()> {
|
||||||
|
if currency.len() != 3 {
|
||||||
|
return Err(anyhow::anyhow!("Invalid currency code length: {}", currency));
|
||||||
|
}
|
||||||
|
if !currency.chars().all(|c| c.is_ascii_uppercase()) {
|
||||||
|
return Err(anyhow::anyhow!("Invalid currency code format: {}", currency));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use gocardless_client::models::{TransactionAmount, CurrencyExchange};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_normal_transaction() {
|
||||||
|
let t = Transaction {
|
||||||
|
transaction_id: Some("123".into()),
|
||||||
|
booking_date: Some("2023-01-01".into()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: TransactionAmount {
|
||||||
|
amount: "100.50".into(),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Shop".into()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: Some("Groceries".into()),
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = map_transaction(t).unwrap();
|
||||||
|
assert_eq!(res.internal_id, "123");
|
||||||
|
assert_eq!(res.amount, Decimal::new(10050, 2));
|
||||||
|
assert_eq!(res.currency, "EUR");
|
||||||
|
assert_eq!(res.foreign_amount, None);
|
||||||
|
assert_eq!(res.description, "Groceries");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_multicurrency_transaction() {
|
||||||
|
let t = Transaction {
|
||||||
|
transaction_id: Some("124".into()),
|
||||||
|
booking_date: Some("2023-01-02".into()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: TransactionAmount {
|
||||||
|
amount: "-10.00".into(),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
},
|
||||||
|
currency_exchange: Some(vec![CurrencyExchange {
|
||||||
|
source_currency: Some("USD".into()),
|
||||||
|
exchange_rate: Some("1.10".into()),
|
||||||
|
unit_currency: None,
|
||||||
|
target_currency: Some("EUR".into()),
|
||||||
|
}]),
|
||||||
|
creditor_name: Some("US Shop".into()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: None,
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = map_transaction(t).unwrap();
|
||||||
|
assert_eq!(res.internal_id, "124");
|
||||||
|
assert_eq!(res.amount, Decimal::new(-1000, 2));
|
||||||
|
assert_eq!(res.foreign_currency, Some("USD".to_string()));
|
||||||
|
|
||||||
|
// 10.00 * 1.10 = 11.00. Sign should be preserved (-11.00)
|
||||||
|
assert_eq!(res.foreign_amount, Some(Decimal::new(-1100, 2)));
|
||||||
|
|
||||||
|
// Description fallback to creditor name
|
||||||
|
assert_eq!(res.description, "US Shop");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_amount_zero() {
|
||||||
|
let amount = Decimal::ZERO;
|
||||||
|
assert!(validate_amount(&amount).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_amount_too_large() {
|
||||||
|
let amount = Decimal::new(2_000_000_000, 0);
|
||||||
|
assert!(validate_amount(&amount).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_currency_invalid_length() {
|
||||||
|
assert!(validate_currency("EU").is_err());
|
||||||
|
assert!(validate_currency("EURO").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_currency_not_uppercase() {
|
||||||
|
assert!(validate_currency("eur").is_err());
|
||||||
|
assert!(validate_currency("EuR").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_currency_valid() {
|
||||||
|
assert!(validate_currency("EUR").is_ok());
|
||||||
|
assert!(validate_currency("USD").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_transaction_invalid_amount() {
|
||||||
|
let t = Transaction {
|
||||||
|
transaction_id: Some("125".into()),
|
||||||
|
booking_date: Some("2023-01-03".into()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: TransactionAmount {
|
||||||
|
amount: "0.00".into(),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Test".into()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: None,
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(map_transaction(t).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_transaction_invalid_currency() {
|
||||||
|
let t = Transaction {
|
||||||
|
transaction_id: Some("126".into()),
|
||||||
|
booking_date: Some("2023-01-04".into()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: TransactionAmount {
|
||||||
|
amount: "100.00".into(),
|
||||||
|
currency: "euro".into(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Test".into()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: None,
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(map_transaction(t).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_transaction_invalid_foreign_amount() {
|
||||||
|
let t = Transaction {
|
||||||
|
transaction_id: Some("127".into()),
|
||||||
|
booking_date: Some("2023-01-05".into()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: TransactionAmount {
|
||||||
|
amount: "-10.00".into(),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
},
|
||||||
|
currency_exchange: Some(vec![CurrencyExchange {
|
||||||
|
source_currency: Some("USD".into()),
|
||||||
|
exchange_rate: Some("0".into()), // This will make foreign_amount zero
|
||||||
|
unit_currency: None,
|
||||||
|
target_currency: Some("EUR".into()),
|
||||||
|
}]),
|
||||||
|
creditor_name: Some("Test".into()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: None,
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(map_transaction(t).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_transaction_invalid_foreign_currency() {
|
||||||
|
let t = Transaction {
|
||||||
|
transaction_id: Some("128".into()),
|
||||||
|
booking_date: Some("2023-01-06".into()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: TransactionAmount {
|
||||||
|
amount: "-10.00".into(),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
},
|
||||||
|
currency_exchange: Some(vec![CurrencyExchange {
|
||||||
|
source_currency: Some("usd".into()), // lowercase
|
||||||
|
exchange_rate: Some("1.10".into()),
|
||||||
|
unit_currency: None,
|
||||||
|
target_currency: Some("EUR".into()),
|
||||||
|
}]),
|
||||||
|
creditor_name: Some("Test".into()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: None,
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(map_transaction(t).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
5
banks2ff/src/adapters/gocardless/mod.rs
Normal file
5
banks2ff/src/adapters/gocardless/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod client;
|
||||||
|
pub mod mapper;
|
||||||
|
pub mod cache;
|
||||||
|
pub mod encryption;
|
||||||
|
pub mod transaction_cache;
|
||||||
557
banks2ff/src/adapters/gocardless/transaction_cache.rs
Normal file
557
banks2ff/src/adapters/gocardless/transaction_cache.rs
Normal file
@@ -0,0 +1,557 @@
|
|||||||
|
use chrono::{NaiveDate, Days};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use anyhow::Result;
|
||||||
|
use crate::adapters::gocardless::encryption::Encryption;
|
||||||
|
use gocardless_client::models::Transaction;
|
||||||
|
use rand;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct AccountTransactionCache {
|
||||||
|
pub account_id: String,
|
||||||
|
pub ranges: Vec<CachedRange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct CachedRange {
|
||||||
|
pub start_date: NaiveDate,
|
||||||
|
pub end_date: NaiveDate,
|
||||||
|
pub transactions: Vec<Transaction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountTransactionCache {
|
||||||
|
/// Get cache file path for an account
|
||||||
|
fn get_cache_path(account_id: &str) -> String {
|
||||||
|
let cache_dir = std::env::var("BANKS2FF_CACHE_DIR").unwrap_or_else(|_| "data/cache".to_string());
|
||||||
|
format!("{}/transactions/{}.enc", cache_dir, account_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load cache from disk
|
||||||
|
pub fn load(account_id: &str) -> Result<Self> {
|
||||||
|
let path = Self::get_cache_path(account_id);
|
||||||
|
|
||||||
|
if !Path::new(&path).exists() {
|
||||||
|
// Return empty cache if file doesn't exist
|
||||||
|
return Ok(Self {
|
||||||
|
account_id: account_id.to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read encrypted data
|
||||||
|
let encrypted_data = std::fs::read(&path)?;
|
||||||
|
let json_data = Encryption::decrypt(&encrypted_data)?;
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
let cache: Self = serde_json::from_slice(&json_data)?;
|
||||||
|
Ok(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save cache to disk
|
||||||
|
pub fn save(&self) -> Result<()> {
|
||||||
|
// Serialize to JSON
|
||||||
|
let json_data = serde_json::to_vec(self)?;
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
let encrypted_data = Encryption::encrypt(&json_data)?;
|
||||||
|
|
||||||
|
// Write to file (create directory if needed)
|
||||||
|
let path = Self::get_cache_path(&self.account_id);
|
||||||
|
if let Some(parent) = std::path::Path::new(&path).parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
std::fs::write(path, encrypted_data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cached transactions within date range
|
||||||
|
pub fn get_cached_transactions(&self, start: NaiveDate, end: NaiveDate) -> Vec<Transaction> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for range in &self.ranges {
|
||||||
|
if Self::ranges_overlap(range.start_date, range.end_date, start, end) {
|
||||||
|
for tx in &range.transactions {
|
||||||
|
if let Some(booking_date_str) = &tx.booking_date {
|
||||||
|
if let Ok(booking_date) = NaiveDate::parse_from_str(booking_date_str, "%Y-%m-%d") {
|
||||||
|
if booking_date >= start && booking_date <= end {
|
||||||
|
result.push(tx.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get uncovered date ranges within requested period
|
||||||
|
pub fn get_uncovered_ranges(&self, start: NaiveDate, end: NaiveDate) -> Vec<(NaiveDate, NaiveDate)> {
|
||||||
|
let mut covered_periods: Vec<(NaiveDate, NaiveDate)> = self.ranges
|
||||||
|
.iter()
|
||||||
|
.filter_map(|range| {
|
||||||
|
if Self::ranges_overlap(range.start_date, range.end_date, start, end) {
|
||||||
|
let overlap_start = range.start_date.max(start);
|
||||||
|
let overlap_end = range.end_date.min(end);
|
||||||
|
if overlap_start <= overlap_end {
|
||||||
|
Some((overlap_start, overlap_end))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
covered_periods.sort_by_key(|&(s, _)| s);
|
||||||
|
|
||||||
|
// Merge overlapping covered periods
|
||||||
|
let mut merged_covered: Vec<(NaiveDate, NaiveDate)> = Vec::new();
|
||||||
|
for period in covered_periods {
|
||||||
|
if let Some(last) = merged_covered.last_mut() {
|
||||||
|
if last.1 >= period.0 {
|
||||||
|
last.1 = last.1.max(period.1);
|
||||||
|
} else {
|
||||||
|
merged_covered.push(period);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
merged_covered.push(period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find gaps
|
||||||
|
let mut uncovered = Vec::new();
|
||||||
|
let mut current_start = start;
|
||||||
|
for (cov_start, cov_end) in merged_covered {
|
||||||
|
if current_start < cov_start {
|
||||||
|
uncovered.push((current_start, cov_start - Days::new(1)));
|
||||||
|
}
|
||||||
|
current_start = cov_end + Days::new(1);
|
||||||
|
}
|
||||||
|
if current_start <= end {
|
||||||
|
uncovered.push((current_start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
uncovered
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store transactions for a date range, merging with existing cache
|
||||||
|
pub fn store_transactions(&mut self, start: NaiveDate, end: NaiveDate, mut transactions: Vec<Transaction>) {
|
||||||
|
Self::deduplicate_transactions(&mut transactions);
|
||||||
|
let new_range = CachedRange {
|
||||||
|
start_date: start,
|
||||||
|
end_date: end,
|
||||||
|
transactions,
|
||||||
|
};
|
||||||
|
self.merge_ranges(new_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge a new range into existing ranges
|
||||||
|
pub fn merge_ranges(&mut self, new_range: CachedRange) {
|
||||||
|
// Find overlapping or adjacent ranges
|
||||||
|
let mut to_merge = Vec::new();
|
||||||
|
let mut remaining = Vec::new();
|
||||||
|
|
||||||
|
for range in &self.ranges {
|
||||||
|
if Self::ranges_overlap_or_adjacent(range.start_date, range.end_date, new_range.start_date, new_range.end_date) {
|
||||||
|
to_merge.push(range.clone());
|
||||||
|
} else {
|
||||||
|
remaining.push(range.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all overlapping/adjacent ranges including the new one
|
||||||
|
to_merge.push(new_range);
|
||||||
|
|
||||||
|
let merged = Self::merge_range_list(to_merge);
|
||||||
|
|
||||||
|
// Update ranges
|
||||||
|
self.ranges = remaining;
|
||||||
|
self.ranges.extend(merged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if two date ranges overlap
|
||||||
|
fn ranges_overlap(start1: NaiveDate, end1: NaiveDate, start2: NaiveDate, end2: NaiveDate) -> bool {
|
||||||
|
start1 <= end2 && start2 <= end1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if two date ranges overlap or are adjacent
|
||||||
|
fn ranges_overlap_or_adjacent(start1: NaiveDate, end1: NaiveDate, start2: NaiveDate, end2: NaiveDate) -> bool {
|
||||||
|
Self::ranges_overlap(start1, end1, start2, end2) ||
|
||||||
|
(end1 + Days::new(1)) == start2 ||
|
||||||
|
(end2 + Days::new(1)) == start1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge a list of ranges into minimal set
|
||||||
|
fn merge_range_list(ranges: Vec<CachedRange>) -> Vec<CachedRange> {
|
||||||
|
if ranges.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by start date
|
||||||
|
let mut sorted = ranges;
|
||||||
|
sorted.sort_by_key(|r| r.start_date);
|
||||||
|
|
||||||
|
let mut merged = Vec::new();
|
||||||
|
let mut current = sorted[0].clone();
|
||||||
|
|
||||||
|
for range in sorted.into_iter().skip(1) {
|
||||||
|
if Self::ranges_overlap_or_adjacent(current.start_date, current.end_date, range.start_date, range.end_date) {
|
||||||
|
// Merge
|
||||||
|
current.start_date = current.start_date.min(range.start_date);
|
||||||
|
current.end_date = current.end_date.max(range.end_date);
|
||||||
|
// Deduplicate transactions
|
||||||
|
current.transactions.extend(range.transactions);
|
||||||
|
Self::deduplicate_transactions(&mut current.transactions);
|
||||||
|
} else {
|
||||||
|
merged.push(current);
|
||||||
|
current = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merged.push(current);
|
||||||
|
|
||||||
|
merged
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deduplicate transactions by transaction_id
|
||||||
|
fn deduplicate_transactions(transactions: &mut Vec<Transaction>) {
|
||||||
|
let mut seen = std::collections::HashSet::new();
|
||||||
|
transactions.retain(|tx| {
|
||||||
|
if let Some(id) = &tx.transaction_id {
|
||||||
|
seen.insert(id.clone())
|
||||||
|
} else {
|
||||||
|
true // Keep if no id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::env;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
|
fn setup_test_env(test_name: &str) -> String {
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
// Use a unique cache directory for each test to avoid interference
|
||||||
|
// Include random component and timestamp for true parallelism safety
|
||||||
|
let random_suffix = rand::random::<u64>();
|
||||||
|
let timestamp = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos();
|
||||||
|
let cache_dir = format!("tmp/test-cache-{}-{}-{}", test_name, random_suffix, timestamp);
|
||||||
|
env::set_var("BANKS2FF_CACHE_DIR", cache_dir.clone());
|
||||||
|
cache_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_test_dir(cache_dir: &str) {
|
||||||
|
// Wait a bit longer to ensure all file operations are complete
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
|
||||||
|
// Try multiple times in case of temporary file locks
|
||||||
|
for _ in 0..5 {
|
||||||
|
if std::path::Path::new(cache_dir).exists() {
|
||||||
|
if std::fs::remove_dir_all(cache_dir).is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break; // Directory already gone
|
||||||
|
}
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_nonexistent_cache() {
|
||||||
|
let cache_dir = setup_test_env("nonexistent");
|
||||||
|
let cache = AccountTransactionCache::load("nonexistent").unwrap();
|
||||||
|
assert_eq!(cache.account_id, "nonexistent");
|
||||||
|
assert!(cache.ranges.is_empty());
|
||||||
|
cleanup_test_dir(&cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_save_and_load_empty_cache() {
|
||||||
|
let cache_dir = setup_test_env("empty");
|
||||||
|
|
||||||
|
let cache = AccountTransactionCache {
|
||||||
|
account_id: "test_account_empty".to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure env vars are set before save
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
// Ensure env vars are set before save
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
// Save
|
||||||
|
cache.save().expect("Save should succeed");
|
||||||
|
|
||||||
|
// Ensure env vars are set before load
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
// Load
|
||||||
|
let loaded = AccountTransactionCache::load("test_account_empty").expect("Load should succeed");
|
||||||
|
|
||||||
|
assert_eq!(loaded.account_id, "test_account_empty");
|
||||||
|
assert!(loaded.ranges.is_empty());
|
||||||
|
|
||||||
|
cleanup_test_dir(&cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_save_and_load_with_data() {
|
||||||
|
let cache_dir = setup_test_env("data");
|
||||||
|
|
||||||
|
let transaction = Transaction {
|
||||||
|
transaction_id: Some("test-tx-1".to_string()),
|
||||||
|
booking_date: Some("2024-01-01".to_string()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
||||||
|
amount: "100.00".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Test Creditor".to_string()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: Some("Test payment".to_string()),
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let range = CachedRange {
|
||||||
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
||||||
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 31).unwrap(),
|
||||||
|
transactions: vec![transaction],
|
||||||
|
};
|
||||||
|
|
||||||
|
let cache = AccountTransactionCache {
|
||||||
|
account_id: "test_account_data".to_string(),
|
||||||
|
ranges: vec![range],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure env vars are set before save
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
// Save
|
||||||
|
cache.save().expect("Save should succeed");
|
||||||
|
|
||||||
|
// Ensure env vars are set before load
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
// Load
|
||||||
|
let loaded = AccountTransactionCache::load("test_account_data").expect("Load should succeed");
|
||||||
|
|
||||||
|
assert_eq!(loaded.account_id, "test_account_data");
|
||||||
|
assert_eq!(loaded.ranges.len(), 1);
|
||||||
|
assert_eq!(loaded.ranges[0].transactions.len(), 1);
|
||||||
|
assert_eq!(loaded.ranges[0].transactions[0].transaction_id, Some("test-tx-1".to_string()));
|
||||||
|
|
||||||
|
cleanup_test_dir(&cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_save_load_different_accounts() {
|
||||||
|
let cache_dir = setup_test_env("different_accounts");
|
||||||
|
|
||||||
|
// Save cache for account A
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
let cache_a = AccountTransactionCache {
|
||||||
|
account_id: "account_a".to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
};
|
||||||
|
cache_a.save().unwrap();
|
||||||
|
|
||||||
|
// Save cache for account B
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
let cache_b = AccountTransactionCache {
|
||||||
|
account_id: "account_b".to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
};
|
||||||
|
cache_b.save().unwrap();
|
||||||
|
|
||||||
|
// Load account A
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
let loaded_a = AccountTransactionCache::load("account_a").unwrap();
|
||||||
|
assert_eq!(loaded_a.account_id, "account_a");
|
||||||
|
|
||||||
|
// Load account B
|
||||||
|
env::set_var("BANKS2FF_CACHE_KEY", "test-cache-key");
|
||||||
|
let loaded_b = AccountTransactionCache::load("account_b").unwrap();
|
||||||
|
assert_eq!(loaded_b.account_id, "account_b");
|
||||||
|
|
||||||
|
cleanup_test_dir(&cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_uncovered_ranges_no_cache() {
|
||||||
|
let cache = AccountTransactionCache {
|
||||||
|
account_id: "test".to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
};
|
||||||
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
||||||
|
let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
|
||||||
|
let uncovered = cache.get_uncovered_ranges(start, end);
|
||||||
|
assert_eq!(uncovered, vec![(start, end)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_uncovered_ranges_full_coverage() {
|
||||||
|
let range = CachedRange {
|
||||||
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
||||||
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 31).unwrap(),
|
||||||
|
transactions: Vec::new(),
|
||||||
|
};
|
||||||
|
let cache = AccountTransactionCache {
|
||||||
|
account_id: "test".to_string(),
|
||||||
|
ranges: vec![range],
|
||||||
|
};
|
||||||
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
||||||
|
let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
|
||||||
|
let uncovered = cache.get_uncovered_ranges(start, end);
|
||||||
|
assert!(uncovered.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_uncovered_ranges_partial_coverage() {
|
||||||
|
let range = CachedRange {
|
||||||
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 10).unwrap(),
|
||||||
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 20).unwrap(),
|
||||||
|
transactions: Vec::new(),
|
||||||
|
};
|
||||||
|
let cache = AccountTransactionCache {
|
||||||
|
account_id: "test".to_string(),
|
||||||
|
ranges: vec![range],
|
||||||
|
};
|
||||||
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
||||||
|
let end = NaiveDate::from_ymd_opt(2024, 1, 31).unwrap();
|
||||||
|
let uncovered = cache.get_uncovered_ranges(start, end);
|
||||||
|
assert_eq!(uncovered.len(), 2);
|
||||||
|
assert_eq!(uncovered[0], (NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(), NaiveDate::from_ymd_opt(2024, 1, 9).unwrap()));
|
||||||
|
assert_eq!(uncovered[1], (NaiveDate::from_ymd_opt(2024, 1, 21).unwrap(), NaiveDate::from_ymd_opt(2024, 1, 31).unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_store_transactions_and_merge() {
|
||||||
|
let mut cache = AccountTransactionCache {
|
||||||
|
account_id: "test".to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
};
|
||||||
|
let start1 = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
||||||
|
let end1 = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
|
||||||
|
let tx1 = Transaction {
|
||||||
|
transaction_id: Some("tx1".to_string()),
|
||||||
|
booking_date: Some("2024-01-05".to_string()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
||||||
|
amount: "100.00".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Creditor".to_string()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: Some("Payment".to_string()),
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
cache.store_transactions(start1, end1, vec![tx1]);
|
||||||
|
|
||||||
|
assert_eq!(cache.ranges.len(), 1);
|
||||||
|
assert_eq!(cache.ranges[0].start_date, start1);
|
||||||
|
assert_eq!(cache.ranges[0].end_date, end1);
|
||||||
|
assert_eq!(cache.ranges[0].transactions.len(), 1);
|
||||||
|
|
||||||
|
// Add overlapping range
|
||||||
|
let start2 = NaiveDate::from_ymd_opt(2024, 1, 5).unwrap();
|
||||||
|
let end2 = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
|
||||||
|
let tx2 = Transaction {
|
||||||
|
transaction_id: Some("tx2".to_string()),
|
||||||
|
booking_date: Some("2024-01-12".to_string()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
||||||
|
amount: "200.00".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Creditor2".to_string()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: Some("Payment2".to_string()),
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
cache.store_transactions(start2, end2, vec![tx2]);
|
||||||
|
|
||||||
|
// Should merge into one range
|
||||||
|
assert_eq!(cache.ranges.len(), 1);
|
||||||
|
assert_eq!(cache.ranges[0].start_date, start1);
|
||||||
|
assert_eq!(cache.ranges[0].end_date, end2);
|
||||||
|
assert_eq!(cache.ranges[0].transactions.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transaction_deduplication() {
|
||||||
|
let mut cache = AccountTransactionCache {
|
||||||
|
account_id: "test".to_string(),
|
||||||
|
ranges: Vec::new(),
|
||||||
|
};
|
||||||
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
||||||
|
let end = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
|
||||||
|
let tx1 = Transaction {
|
||||||
|
transaction_id: Some("dup".to_string()),
|
||||||
|
booking_date: Some("2024-01-05".to_string()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
||||||
|
amount: "100.00".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Creditor".to_string()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: Some("Payment".to_string()),
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
let tx2 = tx1.clone(); // Duplicate
|
||||||
|
cache.store_transactions(start, end, vec![tx1, tx2]);
|
||||||
|
|
||||||
|
assert_eq!(cache.ranges[0].transactions.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_cached_transactions() {
|
||||||
|
let tx1 = Transaction {
|
||||||
|
transaction_id: Some("tx1".to_string()),
|
||||||
|
booking_date: Some("2024-01-05".to_string()),
|
||||||
|
value_date: None,
|
||||||
|
transaction_amount: gocardless_client::models::TransactionAmount {
|
||||||
|
amount: "100.00".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
},
|
||||||
|
currency_exchange: None,
|
||||||
|
creditor_name: Some("Creditor".to_string()),
|
||||||
|
creditor_account: None,
|
||||||
|
debtor_name: None,
|
||||||
|
debtor_account: None,
|
||||||
|
remittance_information_unstructured: Some("Payment".to_string()),
|
||||||
|
proprietary_bank_transaction_code: None,
|
||||||
|
};
|
||||||
|
let range = CachedRange {
|
||||||
|
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
||||||
|
end_date: NaiveDate::from_ymd_opt(2024, 1, 31).unwrap(),
|
||||||
|
transactions: vec![tx1],
|
||||||
|
};
|
||||||
|
let cache = AccountTransactionCache {
|
||||||
|
account_id: "test".to_string(),
|
||||||
|
ranges: vec![range],
|
||||||
|
};
|
||||||
|
let start = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
|
||||||
|
let end = NaiveDate::from_ymd_opt(2024, 1, 10).unwrap();
|
||||||
|
let cached = cache.get_cached_transactions(start, end);
|
||||||
|
assert_eq!(cached.len(), 1);
|
||||||
|
assert_eq!(cached[0].transaction_id, Some("tx1".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
2
banks2ff/src/adapters/mod.rs
Normal file
2
banks2ff/src/adapters/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod firefly;
|
||||||
|
pub mod gocardless;
|
||||||
3
banks2ff/src/core/mod.rs
Normal file
3
banks2ff/src/core/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod models;
|
||||||
|
pub mod ports;
|
||||||
|
pub mod sync;
|
||||||
119
banks2ff/src/core/models.rs
Normal file
119
banks2ff/src/core/models.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
use rust_decimal::Decimal;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use std::fmt;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct BankTransaction {
|
||||||
|
/// Source ID (GoCardless transactionId)
|
||||||
|
pub internal_id: String,
|
||||||
|
/// Booking date
|
||||||
|
pub date: NaiveDate,
|
||||||
|
/// Amount in account currency
|
||||||
|
pub amount: Decimal,
|
||||||
|
/// Account currency code (e.g., EUR)
|
||||||
|
pub currency: String,
|
||||||
|
/// Original amount (if currency exchange occurred)
|
||||||
|
pub foreign_amount: Option<Decimal>,
|
||||||
|
/// Original currency code
|
||||||
|
pub foreign_currency: Option<String>,
|
||||||
|
/// Remittance info or description
|
||||||
|
pub description: String,
|
||||||
|
/// Counterparty name
|
||||||
|
pub counterparty_name: Option<String>,
|
||||||
|
/// Counterparty IBAN
|
||||||
|
pub counterparty_iban: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for BankTransaction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("BankTransaction")
|
||||||
|
.field("internal_id", &self.internal_id)
|
||||||
|
.field("date", &self.date)
|
||||||
|
.field("amount", &"[REDACTED]")
|
||||||
|
.field("currency", &self.currency)
|
||||||
|
.field("foreign_amount", &self.foreign_amount.as_ref().map(|_| "[REDACTED]"))
|
||||||
|
.field("foreign_currency", &self.foreign_currency)
|
||||||
|
.field("description", &"[REDACTED]")
|
||||||
|
.field("counterparty_name", &self.counterparty_name.as_ref().map(|_| "[REDACTED]"))
|
||||||
|
.field("counterparty_iban", &self.counterparty_iban.as_ref().map(|_| "[REDACTED]"))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct Account {
|
||||||
|
pub id: String,
|
||||||
|
pub iban: String,
|
||||||
|
pub currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Account {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Account")
|
||||||
|
.field("id", &self.id)
|
||||||
|
.field("iban", &"[REDACTED]")
|
||||||
|
.field("currency", &self.currency)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bank_transaction_debug_masks_sensitive_data() {
|
||||||
|
let tx = BankTransaction {
|
||||||
|
internal_id: "test-id".to_string(),
|
||||||
|
date: NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
|
||||||
|
amount: Decimal::new(12345, 2), // 123.45
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
foreign_amount: Some(Decimal::new(67890, 2)), // 678.90
|
||||||
|
foreign_currency: Some("USD".to_string()),
|
||||||
|
description: "Test transaction".to_string(),
|
||||||
|
counterparty_name: Some("Test Counterparty".to_string()),
|
||||||
|
counterparty_iban: Some("DE1234567890".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let debug_str = format!("{:?}", tx);
|
||||||
|
assert!(debug_str.contains("internal_id"));
|
||||||
|
assert!(debug_str.contains("date"));
|
||||||
|
assert!(debug_str.contains("currency"));
|
||||||
|
assert!(debug_str.contains("foreign_currency"));
|
||||||
|
assert!(debug_str.contains("[REDACTED]"));
|
||||||
|
assert!(!debug_str.contains("123.45"));
|
||||||
|
assert!(!debug_str.contains("678.90"));
|
||||||
|
assert!(!debug_str.contains("Test transaction"));
|
||||||
|
assert!(!debug_str.contains("Test Counterparty"));
|
||||||
|
assert!(!debug_str.contains("DE1234567890"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_account_debug_masks_iban() {
|
||||||
|
let account = Account {
|
||||||
|
id: "123".to_string(),
|
||||||
|
iban: "DE1234567890".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let debug_str = format!("{:?}", account);
|
||||||
|
assert!(debug_str.contains("id"));
|
||||||
|
assert!(debug_str.contains("currency"));
|
||||||
|
assert!(debug_str.contains("[REDACTED]"));
|
||||||
|
assert!(!debug_str.contains("DE1234567890"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum SyncError {
|
||||||
|
#[error("End User Agreement {agreement_id} has expired")]
|
||||||
|
AgreementExpired { agreement_id: String },
|
||||||
|
#[error("Account {account_id} skipped: {reason}")]
|
||||||
|
AccountSkipped { account_id: String, reason: String },
|
||||||
|
#[error("Source error: {0}")]
|
||||||
|
SourceError(anyhow::Error),
|
||||||
|
#[error("Destination error: {0}")]
|
||||||
|
DestinationError(anyhow::Error),
|
||||||
|
}
|
||||||
82
banks2ff/src/core/ports.rs
Normal file
82
banks2ff/src/core/ports.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use anyhow::Result;
|
||||||
|
#[cfg(test)]
|
||||||
|
use mockall::automock;
|
||||||
|
use crate::core::models::{BankTransaction, Account};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct IngestResult {
|
||||||
|
pub created: usize,
|
||||||
|
pub duplicates: usize,
|
||||||
|
pub errors: usize,
|
||||||
|
pub healed: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, automock)]
|
||||||
|
#[async_trait]
|
||||||
|
pub trait TransactionSource: Send + Sync {
|
||||||
|
/// Fetch accounts. Optionally filter by a list of wanted IBANs to save requests.
|
||||||
|
async fn get_accounts(&self, wanted_ibans: Option<Vec<String>>) -> Result<Vec<Account>>;
|
||||||
|
async fn get_transactions(&self, account_id: &str, start: NaiveDate, end: NaiveDate) -> Result<Vec<BankTransaction>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blanket implementation for references
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: TransactionSource> TransactionSource for &T {
|
||||||
|
async fn get_accounts(&self, wanted_ibans: Option<Vec<String>>) -> Result<Vec<Account>> {
|
||||||
|
(**self).get_accounts(wanted_ibans).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_transactions(&self, account_id: &str, start: NaiveDate, end: NaiveDate) -> Result<Vec<BankTransaction>> {
|
||||||
|
(**self).get_transactions(account_id, start, end).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TransactionMatch {
|
||||||
|
pub id: String,
|
||||||
|
pub has_external_id: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, automock)]
|
||||||
|
#[async_trait]
|
||||||
|
pub trait TransactionDestination: Send + Sync {
|
||||||
|
async fn resolve_account_id(&self, iban: &str) -> Result<Option<String>>;
|
||||||
|
/// Get list of all active asset account IBANs to drive the sync
|
||||||
|
async fn get_active_account_ibans(&self) -> Result<Vec<String>>;
|
||||||
|
|
||||||
|
// New granular methods for Healer Logic
|
||||||
|
async fn get_last_transaction_date(&self, account_id: &str) -> Result<Option<NaiveDate>>;
|
||||||
|
async fn find_transaction(&self, account_id: &str, transaction: &BankTransaction) -> Result<Option<TransactionMatch>>;
|
||||||
|
async fn create_transaction(&self, account_id: &str, tx: &BankTransaction) -> Result<()>;
|
||||||
|
async fn update_transaction_external_id(&self, id: &str, external_id: &str) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blanket implementation for references
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: TransactionDestination> TransactionDestination for &T {
|
||||||
|
async fn resolve_account_id(&self, iban: &str) -> Result<Option<String>> {
|
||||||
|
(**self).resolve_account_id(iban).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_active_account_ibans(&self) -> Result<Vec<String>> {
|
||||||
|
(**self).get_active_account_ibans().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_last_transaction_date(&self, account_id: &str) -> Result<Option<NaiveDate>> {
|
||||||
|
(**self).get_last_transaction_date(account_id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_transaction(&self, account_id: &str, transaction: &BankTransaction) -> Result<Option<TransactionMatch>> {
|
||||||
|
(**self).find_transaction(account_id, transaction).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_transaction(&self, account_id: &str, tx: &BankTransaction) -> Result<()> {
|
||||||
|
(**self).create_transaction(account_id, tx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_transaction_external_id(&self, id: &str, external_id: &str) -> Result<()> {
|
||||||
|
(**self).update_transaction_external_id(id, external_id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
353
banks2ff/src/core/sync.rs
Normal file
353
banks2ff/src/core/sync.rs
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use tracing::{info, warn, instrument};
|
||||||
|
use crate::core::ports::{IngestResult, TransactionSource, TransactionDestination};
|
||||||
|
use crate::core::models::{SyncError, Account};
|
||||||
|
use chrono::{NaiveDate, Local};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SyncResult {
|
||||||
|
pub ingest: IngestResult,
|
||||||
|
pub accounts_processed: usize,
|
||||||
|
pub accounts_skipped_expired: usize,
|
||||||
|
pub accounts_skipped_errors: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(source, destination))]
|
||||||
|
pub async fn run_sync(
|
||||||
|
source: impl TransactionSource,
|
||||||
|
destination: impl TransactionDestination,
|
||||||
|
cli_start_date: Option<NaiveDate>,
|
||||||
|
cli_end_date: Option<NaiveDate>,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<SyncResult> {
|
||||||
|
info!("Starting synchronization...");
|
||||||
|
|
||||||
|
// Optimization: Get active Firefly IBANs first
|
||||||
|
let wanted_ibans = destination.get_active_account_ibans().await.map_err(SyncError::DestinationError)?;
|
||||||
|
info!("Syncing {} active accounts from Firefly III", wanted_ibans.len());
|
||||||
|
|
||||||
|
let accounts = source.get_accounts(Some(wanted_ibans)).await.map_err(SyncError::SourceError)?;
|
||||||
|
info!("Found {} accounts from source", accounts.len());
|
||||||
|
|
||||||
|
// Default end date is Yesterday
|
||||||
|
let end_date = cli_end_date.unwrap_or_else(|| Local::now().date_naive() - chrono::Duration::days(1));
|
||||||
|
|
||||||
|
let mut result = SyncResult::default();
|
||||||
|
|
||||||
|
for account in accounts {
|
||||||
|
let span = tracing::info_span!("sync_account", account_id = %account.id);
|
||||||
|
let _enter = span.enter();
|
||||||
|
|
||||||
|
info!("Processing account...");
|
||||||
|
|
||||||
|
// Process account with error handling
|
||||||
|
match process_single_account(&source, &destination, &account, cli_start_date, end_date, dry_run).await {
|
||||||
|
Ok(stats) => {
|
||||||
|
result.accounts_processed += 1;
|
||||||
|
result.ingest.created += stats.created;
|
||||||
|
result.ingest.healed += stats.healed;
|
||||||
|
result.ingest.duplicates += stats.duplicates;
|
||||||
|
result.ingest.errors += stats.errors;
|
||||||
|
info!("Account {} sync complete. Created: {}, Healed: {}, Duplicates: {}, Errors: {}",
|
||||||
|
account.id, stats.created, stats.healed, stats.duplicates, stats.errors);
|
||||||
|
}
|
||||||
|
Err(SyncError::AgreementExpired { agreement_id }) => {
|
||||||
|
result.accounts_skipped_expired += 1;
|
||||||
|
warn!("Account {} skipped - associated agreement {} has expired", account.id, agreement_id);
|
||||||
|
}
|
||||||
|
Err(SyncError::AccountSkipped { account_id, reason }) => {
|
||||||
|
result.accounts_skipped_errors += 1;
|
||||||
|
warn!("Account {} skipped: {}", account_id, reason);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
result.accounts_skipped_errors += 1;
|
||||||
|
warn!("Account {} failed with error: {}", account.id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Synchronization finished. Processed: {}, Skipped (expired): {}, Skipped (errors): {}",
|
||||||
|
result.accounts_processed, result.accounts_skipped_expired, result.accounts_skipped_errors);
|
||||||
|
info!("Total transactions - Created: {}, Healed: {}, Duplicates: {}, Errors: {}",
|
||||||
|
result.ingest.created, result.ingest.healed, result.ingest.duplicates, result.ingest.errors);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_single_account(
|
||||||
|
source: &impl TransactionSource,
|
||||||
|
destination: &impl TransactionDestination,
|
||||||
|
account: &Account,
|
||||||
|
cli_start_date: Option<NaiveDate>,
|
||||||
|
end_date: NaiveDate,
|
||||||
|
dry_run: bool,
|
||||||
|
) -> Result<IngestResult, SyncError> {
|
||||||
|
let dest_id_opt = destination.resolve_account_id(&account.iban).await.map_err(SyncError::DestinationError)?;
|
||||||
|
let Some(dest_id) = dest_id_opt else {
|
||||||
|
return Err(SyncError::AccountSkipped {
|
||||||
|
account_id: account.id.clone(),
|
||||||
|
reason: "Not found in destination".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Resolved destination ID: {}", dest_id);
|
||||||
|
|
||||||
|
// Determine Start Date
|
||||||
|
let start_date = if let Some(d) = cli_start_date {
|
||||||
|
d
|
||||||
|
} else {
|
||||||
|
// Default: Latest transaction date + 1 day
|
||||||
|
match destination.get_last_transaction_date(&dest_id).await.map_err(SyncError::DestinationError)? {
|
||||||
|
Some(last_date) => last_date + chrono::Duration::days(1),
|
||||||
|
None => {
|
||||||
|
// If no transaction exists in Firefly, we assume this is a fresh sync.
|
||||||
|
// Default to syncing last 30 days.
|
||||||
|
end_date - chrono::Duration::days(30)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if start_date > end_date {
|
||||||
|
info!("Start date {} is after end date {}. Nothing to sync.", start_date, end_date);
|
||||||
|
return Ok(IngestResult::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Syncing interval: {} to {}", start_date, end_date);
|
||||||
|
|
||||||
|
let transactions = match source.get_transactions(&account.id, start_date, end_date).await {
|
||||||
|
Ok(txns) => txns,
|
||||||
|
Err(e) => {
|
||||||
|
let err_str = e.to_string();
|
||||||
|
if err_str.contains("401") && (err_str.contains("expired") || err_str.contains("EUA")) {
|
||||||
|
return Err(SyncError::AgreementExpired {
|
||||||
|
agreement_id: "unknown".to_string(), // We don't have the agreement ID here
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Err(SyncError::SourceError(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if transactions.is_empty() {
|
||||||
|
info!("No transactions found for period.");
|
||||||
|
return Ok(IngestResult::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Fetched {} transactions from source.", transactions.len());
|
||||||
|
|
||||||
|
let mut stats = IngestResult::default();
|
||||||
|
|
||||||
|
// Healer Logic Loop
|
||||||
|
for tx in transactions {
|
||||||
|
// 1. Check if it exists
|
||||||
|
match destination.find_transaction(&dest_id, &tx).await.map_err(SyncError::DestinationError)? {
|
||||||
|
Some(existing) => {
|
||||||
|
if existing.has_external_id {
|
||||||
|
// Already synced properly
|
||||||
|
stats.duplicates += 1;
|
||||||
|
} else {
|
||||||
|
// Found "naked" transaction -> Heal it
|
||||||
|
if dry_run {
|
||||||
|
info!("[DRY RUN] Would heal transaction {} (Firefly ID: {})", tx.internal_id, existing.id);
|
||||||
|
stats.healed += 1;
|
||||||
|
} else {
|
||||||
|
info!("Healing transaction {} (Firefly ID: {})", tx.internal_id, existing.id);
|
||||||
|
if let Err(e) = destination.update_transaction_external_id(&existing.id, &tx.internal_id).await {
|
||||||
|
tracing::error!("Failed to heal transaction: {}", e);
|
||||||
|
stats.errors += 1;
|
||||||
|
} else {
|
||||||
|
stats.healed += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// New transaction
|
||||||
|
if dry_run {
|
||||||
|
info!("[DRY RUN] Would create transaction {}", tx.internal_id);
|
||||||
|
stats.created += 1;
|
||||||
|
} else {
|
||||||
|
if let Err(e) = destination.create_transaction(&dest_id, &tx).await {
|
||||||
|
// Firefly might still reject it as duplicate if hash matches, even if we didn't find it via heuristic
|
||||||
|
// (unlikely if heuristic is good, but possible)
|
||||||
|
let err_str = e.to_string();
|
||||||
|
if err_str.contains("422") || err_str.contains("Duplicate") {
|
||||||
|
warn!("Duplicate rejected by Firefly: {}", tx.internal_id);
|
||||||
|
stats.duplicates += 1;
|
||||||
|
} else {
|
||||||
|
tracing::error!("Failed to create transaction: {}", e);
|
||||||
|
stats.errors += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stats.created += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::core::ports::{MockTransactionSource, MockTransactionDestination, TransactionMatch};
|
||||||
|
use crate::core::models::{Account, BankTransaction};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
use mockall::predicate::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_sync_flow_create_new() {
|
||||||
|
let mut source = MockTransactionSource::new();
|
||||||
|
let mut dest = MockTransactionDestination::new();
|
||||||
|
|
||||||
|
// Source setup
|
||||||
|
source.expect_get_accounts()
|
||||||
|
.with(always()) // Match any argument
|
||||||
|
.returning(|_| Ok(vec![Account {
|
||||||
|
id: "src_1".to_string(),
|
||||||
|
iban: "NL01".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
}]));
|
||||||
|
|
||||||
|
let tx = BankTransaction {
|
||||||
|
internal_id: "tx1".into(),
|
||||||
|
date: NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
|
||||||
|
amount: Decimal::new(100, 0),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
foreign_amount: None,
|
||||||
|
foreign_currency: None,
|
||||||
|
description: "Test".into(),
|
||||||
|
counterparty_name: None,
|
||||||
|
counterparty_iban: None,
|
||||||
|
};
|
||||||
|
let tx_clone = tx.clone();
|
||||||
|
|
||||||
|
source.expect_get_transactions()
|
||||||
|
.returning(move |_, _, _| Ok(vec![tx.clone()]));
|
||||||
|
|
||||||
|
// Destination setup
|
||||||
|
dest.expect_get_active_account_ibans()
|
||||||
|
.returning(|| Ok(vec!["NL01".to_string()]));
|
||||||
|
|
||||||
|
dest.expect_resolve_account_id()
|
||||||
|
.returning(|_| Ok(Some("dest_1".into())));
|
||||||
|
|
||||||
|
dest.expect_get_last_transaction_date()
|
||||||
|
.returning(|_| Ok(Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap())));
|
||||||
|
|
||||||
|
// 1. Find -> None
|
||||||
|
dest.expect_find_transaction()
|
||||||
|
.times(1)
|
||||||
|
.returning(|_, _| Ok(None));
|
||||||
|
|
||||||
|
// 2. Create -> Ok
|
||||||
|
dest.expect_create_transaction()
|
||||||
|
.with(eq("dest_1"), eq(tx_clone))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_, _| Ok(()));
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
let res = run_sync(&source, &dest, None, None, false).await;
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_sync_flow_heal_existing() {
|
||||||
|
let mut source = MockTransactionSource::new();
|
||||||
|
let mut dest = MockTransactionDestination::new();
|
||||||
|
|
||||||
|
dest.expect_get_active_account_ibans()
|
||||||
|
.returning(|| Ok(vec!["NL01".to_string()]));
|
||||||
|
|
||||||
|
source.expect_get_accounts()
|
||||||
|
.with(always())
|
||||||
|
.returning(|_| Ok(vec![Account {
|
||||||
|
id: "src_1".to_string(),
|
||||||
|
iban: "NL01".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
}]));
|
||||||
|
|
||||||
|
source.expect_get_transactions()
|
||||||
|
.returning(|_, _, _| Ok(vec![
|
||||||
|
BankTransaction {
|
||||||
|
internal_id: "tx1".into(),
|
||||||
|
date: NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
|
||||||
|
amount: Decimal::new(100, 0),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
foreign_amount: None,
|
||||||
|
foreign_currency: None,
|
||||||
|
description: "Test".into(),
|
||||||
|
counterparty_name: None,
|
||||||
|
counterparty_iban: None,
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
|
dest.expect_resolve_account_id().returning(|_| Ok(Some("dest_1".into())));
|
||||||
|
dest.expect_get_last_transaction_date().returning(|_| Ok(Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap())));
|
||||||
|
|
||||||
|
// 1. Find -> Some(No External ID)
|
||||||
|
dest.expect_find_transaction()
|
||||||
|
.times(1)
|
||||||
|
.returning(|_, _| Ok(Some(TransactionMatch {
|
||||||
|
id: "ff_tx_1".to_string(),
|
||||||
|
has_external_id: false,
|
||||||
|
})));
|
||||||
|
|
||||||
|
// 2. Update -> Ok
|
||||||
|
dest.expect_update_transaction_external_id()
|
||||||
|
.with(eq("ff_tx_1"), eq("tx1"))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_, _| Ok(()));
|
||||||
|
|
||||||
|
let res = run_sync(&source, &dest, None, None, false).await;
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_sync_flow_dry_run() {
|
||||||
|
let mut source = MockTransactionSource::new();
|
||||||
|
let mut dest = MockTransactionDestination::new();
|
||||||
|
|
||||||
|
dest.expect_get_active_account_ibans()
|
||||||
|
.returning(|| Ok(vec!["NL01".to_string()]));
|
||||||
|
|
||||||
|
source.expect_get_accounts()
|
||||||
|
.with(always())
|
||||||
|
.returning(|_| Ok(vec![Account {
|
||||||
|
id: "src_1".to_string(),
|
||||||
|
iban: "NL01".to_string(),
|
||||||
|
currency: "EUR".to_string(),
|
||||||
|
}]));
|
||||||
|
|
||||||
|
let tx = BankTransaction {
|
||||||
|
internal_id: "tx1".into(),
|
||||||
|
date: NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
|
||||||
|
amount: Decimal::new(100, 0),
|
||||||
|
currency: "EUR".into(),
|
||||||
|
foreign_amount: None,
|
||||||
|
foreign_currency: None,
|
||||||
|
description: "Test".into(),
|
||||||
|
counterparty_name: None,
|
||||||
|
counterparty_iban: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
source.expect_get_transactions()
|
||||||
|
.returning(move |_, _, _| Ok(vec![tx.clone()]));
|
||||||
|
|
||||||
|
dest.expect_resolve_account_id().returning(|_| Ok(Some("dest_1".into())));
|
||||||
|
dest.expect_get_last_transaction_date().returning(|_| Ok(Some(NaiveDate::from_ymd_opt(2022, 12, 31).unwrap())));
|
||||||
|
|
||||||
|
// 1. Find -> None (New transaction)
|
||||||
|
dest.expect_find_transaction()
|
||||||
|
.returning(|_, _| Ok(None));
|
||||||
|
|
||||||
|
// 2. Create -> NEVER Called (Dry Run)
|
||||||
|
dest.expect_create_transaction().never();
|
||||||
|
dest.expect_update_transaction_external_id().never();
|
||||||
|
|
||||||
|
let res = run_sync(source, dest, None, None, true).await;
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
116
banks2ff/src/debug.rs
Normal file
116
banks2ff/src/debug.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use reqwest_middleware::{Middleware, Next};
|
||||||
|
use task_local_extensions::Extensions;
|
||||||
|
use reqwest::{Request, Response};
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use chrono::Utc;
|
||||||
|
use hyper::Body;
|
||||||
|
|
||||||
|
static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub struct DebugLogger {
|
||||||
|
service_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugLogger {
|
||||||
|
pub fn new(service_name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
service_name: service_name.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Middleware for DebugLogger {
|
||||||
|
async fn handle(
|
||||||
|
&self,
|
||||||
|
req: Request,
|
||||||
|
extensions: &mut Extensions,
|
||||||
|
next: Next<'_>,
|
||||||
|
) -> reqwest_middleware::Result<Response> {
|
||||||
|
let request_id = REQUEST_COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||||
|
let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
|
||||||
|
let filename = format!("{}_{}_{}.txt", timestamp, request_id, self.service_name);
|
||||||
|
|
||||||
|
let dir = format!("./debug_logs/{}", self.service_name);
|
||||||
|
fs::create_dir_all(&dir).unwrap_or_else(|e| {
|
||||||
|
eprintln!("Failed to create debug log directory: {}", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
let filepath = Path::new(&dir).join(filename);
|
||||||
|
|
||||||
|
let mut log_content = String::new();
|
||||||
|
|
||||||
|
// Curl command
|
||||||
|
log_content.push_str("# Curl command:\n");
|
||||||
|
let curl = build_curl_command(&req);
|
||||||
|
log_content.push_str(&format!("{}\n\n", curl));
|
||||||
|
|
||||||
|
// Request
|
||||||
|
log_content.push_str("# Request:\n");
|
||||||
|
log_content.push_str(&format!("{} {} HTTP/1.1\n", req.method(), req.url()));
|
||||||
|
for (key, value) in req.headers() {
|
||||||
|
log_content.push_str(&format!("{}: {}\n", key, value.to_str().unwrap_or("[INVALID]")));
|
||||||
|
}
|
||||||
|
if let Some(body) = req.body() {
|
||||||
|
if let Some(bytes) = body.as_bytes() {
|
||||||
|
log_content.push_str(&format!("\n{}", String::from_utf8_lossy(bytes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log_content.push_str("\n\n");
|
||||||
|
|
||||||
|
// Send request and get response
|
||||||
|
let response = next.run(req, extensions).await?;
|
||||||
|
|
||||||
|
// Extract parts before consuming body
|
||||||
|
let status = response.status();
|
||||||
|
let version = response.version();
|
||||||
|
let headers = response.headers().clone();
|
||||||
|
|
||||||
|
// Response
|
||||||
|
log_content.push_str("# Response:\n");
|
||||||
|
log_content.push_str(&format!("HTTP/1.1 {} {}\n", status.as_u16(), status.canonical_reason().unwrap_or("Unknown")));
|
||||||
|
for (key, value) in &headers {
|
||||||
|
log_content.push_str(&format!("{}: {}\n", key, value.to_str().unwrap_or("[INVALID]")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read body
|
||||||
|
let body_bytes = response.bytes().await.map_err(|e| reqwest_middleware::Error::Middleware(anyhow::anyhow!("Failed to read response body: {}", e)))?;
|
||||||
|
let body_str = String::from_utf8_lossy(&body_bytes);
|
||||||
|
log_content.push_str(&format!("\n{}", body_str));
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
if let Err(e) = fs::write(&filepath, log_content) {
|
||||||
|
eprintln!("Failed to write debug log: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct response
|
||||||
|
let mut builder = http::Response::builder()
|
||||||
|
.status(status)
|
||||||
|
.version(version);
|
||||||
|
for (key, value) in &headers {
|
||||||
|
builder = builder.header(key, value);
|
||||||
|
}
|
||||||
|
let new_response = builder.body(Body::from(body_bytes)).unwrap();
|
||||||
|
Ok(Response::from(new_response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_curl_command(req: &Request) -> String {
|
||||||
|
let mut curl = format!("curl -v -X {} '{}'", req.method(), req.url());
|
||||||
|
|
||||||
|
for (key, value) in req.headers() {
|
||||||
|
let value_str = value.to_str().unwrap_or("[INVALID]").replace("'", "\\'");
|
||||||
|
curl.push_str(&format!(" -H '{}: {}'", key, value_str));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(body) = req.body() {
|
||||||
|
if let Some(bytes) = body.as_bytes() {
|
||||||
|
let body_str = String::from_utf8_lossy(bytes).replace("'", "\\'");
|
||||||
|
curl.push_str(&format!(" -d '{}'", body_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curl
|
||||||
|
}
|
||||||
102
banks2ff/src/main.rs
Normal file
102
banks2ff/src/main.rs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
mod adapters;
|
||||||
|
mod core;
|
||||||
|
mod debug;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use tracing::{info, error};
|
||||||
|
use crate::adapters::gocardless::client::GoCardlessAdapter;
|
||||||
|
use crate::adapters::firefly::client::FireflyAdapter;
|
||||||
|
use crate::core::sync::run_sync;
|
||||||
|
use crate::debug::DebugLogger;
|
||||||
|
use gocardless_client::client::GoCardlessClient;
|
||||||
|
use firefly_client::client::FireflyClient;
|
||||||
|
use reqwest_middleware::ClientBuilder;
|
||||||
|
use std::env;
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Path to configuration file (optional)
|
||||||
|
#[arg(short, long)]
|
||||||
|
config: Option<String>,
|
||||||
|
|
||||||
|
/// Start date for synchronization (YYYY-MM-DD). Defaults to last transaction date + 1.
|
||||||
|
#[arg(short, long)]
|
||||||
|
start: Option<NaiveDate>,
|
||||||
|
|
||||||
|
/// End date for synchronization (YYYY-MM-DD). Defaults to yesterday.
|
||||||
|
#[arg(short, long)]
|
||||||
|
end: Option<NaiveDate>,
|
||||||
|
|
||||||
|
/// Dry run mode: Do not create or update transactions in Firefly III.
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
dry_run: bool,
|
||||||
|
|
||||||
|
/// Enable debug logging of HTTP requests/responses to ./debug_logs/
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
// Initialize logging
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
info!("Starting banks2ff...");
|
||||||
|
if args.dry_run {
|
||||||
|
info!("DRY RUN MODE ENABLED: No changes will be made to Firefly III.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config Load
|
||||||
|
let gc_url = env::var("GOCARDLESS_URL").unwrap_or_else(|_| "https://bankaccountdata.gocardless.com".to_string());
|
||||||
|
let gc_id = env::var("GOCARDLESS_ID").expect("GOCARDLESS_ID not set");
|
||||||
|
let gc_key = env::var("GOCARDLESS_KEY").expect("GOCARDLESS_KEY not set");
|
||||||
|
|
||||||
|
let ff_url = env::var("FIREFLY_III_URL").expect("FIREFLY_III_URL not set");
|
||||||
|
let ff_key = env::var("FIREFLY_III_API_KEY").expect("FIREFLY_III_API_KEY not set");
|
||||||
|
|
||||||
|
// Clients
|
||||||
|
let gc_client = if args.debug {
|
||||||
|
let client = ClientBuilder::new(reqwest::Client::new())
|
||||||
|
.with(DebugLogger::new("gocardless"))
|
||||||
|
.build();
|
||||||
|
GoCardlessClient::with_client(&gc_url, &gc_id, &gc_key, Some(client))?
|
||||||
|
} else {
|
||||||
|
GoCardlessClient::new(&gc_url, &gc_id, &gc_key)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let ff_client = if args.debug {
|
||||||
|
let client = ClientBuilder::new(reqwest::Client::new())
|
||||||
|
.with(DebugLogger::new("firefly"))
|
||||||
|
.build();
|
||||||
|
FireflyClient::with_client(&ff_url, &ff_key, Some(client))?
|
||||||
|
} else {
|
||||||
|
FireflyClient::new(&ff_url, &ff_key)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adapters
|
||||||
|
let source = GoCardlessAdapter::new(gc_client);
|
||||||
|
let destination = FireflyAdapter::new(ff_client);
|
||||||
|
|
||||||
|
// Run
|
||||||
|
match run_sync(source, destination, args.start, args.end, args.dry_run).await {
|
||||||
|
Ok(result) => {
|
||||||
|
info!("Sync completed successfully.");
|
||||||
|
info!("Accounts processed: {}, skipped (expired): {}, skipped (errors): {}",
|
||||||
|
result.accounts_processed, result.accounts_skipped_expired, result.accounts_skipped_errors);
|
||||||
|
info!("Transactions - Created: {}, Healed: {}, Duplicates: {}, Errors: {}",
|
||||||
|
result.ingest.created, result.ingest.healed, result.ingest.duplicates, result.ingest.errors);
|
||||||
|
}
|
||||||
|
Err(e) => error!("Sync failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
125
docs/architecture.md
Normal file
125
docs/architecture.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# 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)
|
||||||
21
env.example
21
env.example
@@ -1,6 +1,15 @@
|
|||||||
FIREFLY_III_URL=
|
FIREFLY_III_URL=
|
||||||
FIREFLY_III_API_KEY=
|
FIREFLY_III_API_KEY=
|
||||||
FIREFLY_III_CLIENT_ID=
|
FIREFLY_III_CLIENT_ID=
|
||||||
|
|
||||||
GOCARDLESS_KEY=
|
GOCARDLESS_KEY=
|
||||||
GOCARDLESS_ID=
|
GOCARDLESS_ID=
|
||||||
|
|
||||||
|
# Required: Generate a secure random key (32+ characters recommended)
|
||||||
|
# Linux/macOS: od -vAn -N32 -tx1 /dev/urandom | tr -d ' '
|
||||||
|
# Windows PowerShell: [Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 }))
|
||||||
|
# Or use any password manager to generate a strong random string
|
||||||
|
BANKS2FF_CACHE_KEY=
|
||||||
|
|
||||||
|
# Optional: Custom cache directory (defaults to data/cache)
|
||||||
|
# BANKS2FF_CACHE_DIR=
|
||||||
21
firefly-client/Cargo.toml
Normal file
21
firefly-client/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "firefly-client"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { workspace = true, default-features = false, features = ["json", "rustls-tls"] }
|
||||||
|
reqwest-middleware = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
|
rust_decimal = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wiremock = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
tokio-test = { workspace = true }
|
||||||
133
firefly-client/src/client.rs
Normal file
133
firefly-client/src/client.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
use reqwest::Url;
|
||||||
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tracing::instrument;
|
||||||
|
use crate::models::{AccountArray, TransactionStore, TransactionArray, TransactionUpdate};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum FireflyError {
|
||||||
|
#[error("Request failed: {0}")]
|
||||||
|
RequestFailed(#[from] reqwest::Error),
|
||||||
|
#[error("Middleware error: {0}")]
|
||||||
|
MiddlewareError(#[from] reqwest_middleware::Error),
|
||||||
|
#[error("API Error: {0}")]
|
||||||
|
ApiError(String),
|
||||||
|
#[error("URL Parse Error: {0}")]
|
||||||
|
UrlParseError(#[from] url::ParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FireflyClient {
|
||||||
|
base_url: Url,
|
||||||
|
client: ClientWithMiddleware,
|
||||||
|
access_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FireflyClient {
|
||||||
|
pub fn new(base_url: &str, access_token: &str) -> Result<Self, FireflyError> {
|
||||||
|
Self::with_client(base_url, access_token, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_client(base_url: &str, access_token: &str, client: Option<ClientWithMiddleware>) -> Result<Self, FireflyError> {
|
||||||
|
Ok(Self {
|
||||||
|
base_url: Url::parse(base_url)?,
|
||||||
|
client: client.unwrap_or_else(|| reqwest_middleware::ClientBuilder::new(reqwest::Client::new()).build()),
|
||||||
|
access_token: access_token.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn get_accounts(&self, _iban: &str) -> Result<AccountArray, FireflyError> {
|
||||||
|
let mut url = self.base_url.join("/api/v1/accounts")?;
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair("type", "asset");
|
||||||
|
|
||||||
|
self.get_authenticated(url).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn search_accounts(&self, query: &str) -> Result<AccountArray, FireflyError> {
|
||||||
|
let mut url = self.base_url.join("/api/v1/search/accounts")?;
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.append_pair("query", query)
|
||||||
|
.append_pair("type", "asset")
|
||||||
|
.append_pair("field", "all");
|
||||||
|
|
||||||
|
self.get_authenticated(url).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, transaction))]
|
||||||
|
pub async fn store_transaction(&self, transaction: TransactionStore) -> Result<(), FireflyError> {
|
||||||
|
let url = self.base_url.join("/api/v1/transactions")?;
|
||||||
|
|
||||||
|
let response = self.client.post(url)
|
||||||
|
.bearer_auth(&self.access_token)
|
||||||
|
.header("accept", "application/json")
|
||||||
|
.json(&transaction)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let text = response.text().await?;
|
||||||
|
return Err(FireflyError::ApiError(format!("Store Transaction Failed {}: {}", status, text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
|
pub async fn list_account_transactions(&self, account_id: &str, start: Option<&str>, end: Option<&str>) -> Result<TransactionArray, FireflyError> {
|
||||||
|
let mut url = self.base_url.join(&format!("/api/v1/accounts/{}/transactions", account_id))?;
|
||||||
|
{
|
||||||
|
let mut pairs = url.query_pairs_mut();
|
||||||
|
if let Some(s) = start {
|
||||||
|
pairs.append_pair("start", s);
|
||||||
|
}
|
||||||
|
if let Some(e) = end {
|
||||||
|
pairs.append_pair("end", e);
|
||||||
|
}
|
||||||
|
// Limit to 50, could be higher but safer to page if needed. For heuristic checks 50 is usually plenty per day range.
|
||||||
|
pairs.append_pair("limit", "50");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.get_authenticated(url).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self, update))]
|
||||||
|
pub async fn update_transaction(&self, id: &str, update: TransactionUpdate) -> Result<(), FireflyError> {
|
||||||
|
let url = self.base_url.join(&format!("/api/v1/transactions/{}", id))?;
|
||||||
|
|
||||||
|
let response = self.client.put(url)
|
||||||
|
.bearer_auth(&self.access_token)
|
||||||
|
.header("accept", "application/json")
|
||||||
|
.json(&update)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let text = response.text().await?;
|
||||||
|
return Err(FireflyError::ApiError(format!("Update Transaction Failed {}: {}", status, text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_authenticated<T: DeserializeOwned>(&self, url: Url) -> Result<T, FireflyError> {
|
||||||
|
let response = self.client.get(url)
|
||||||
|
.bearer_auth(&self.access_token)
|
||||||
|
.header("accept", "application/json")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let text = response.text().await?;
|
||||||
|
return Err(FireflyError::ApiError(format!("API request failed {}: {}", status, text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = response.json().await?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
2
firefly-client/src/lib.rs
Normal file
2
firefly-client/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod client;
|
||||||
|
pub mod models;
|
||||||
81
firefly-client/src/models.rs
Normal file
81
firefly-client/src/models.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AccountRead {
|
||||||
|
pub id: String,
|
||||||
|
pub attributes: Account,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Account {
|
||||||
|
pub name: String,
|
||||||
|
pub iban: Option<String>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub account_type: String,
|
||||||
|
pub active: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AccountArray {
|
||||||
|
pub data: Vec<AccountRead>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionRead {
|
||||||
|
pub id: String,
|
||||||
|
pub attributes: Transaction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Transaction {
|
||||||
|
pub transactions: Vec<TransactionSplit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionSplit {
|
||||||
|
pub date: String,
|
||||||
|
pub amount: String,
|
||||||
|
pub description: String,
|
||||||
|
pub external_id: Option<String>,
|
||||||
|
pub currency_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionArray {
|
||||||
|
pub data: Vec<TransactionRead>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionSplitStore {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub transaction_type: String,
|
||||||
|
pub date: String,
|
||||||
|
pub amount: String,
|
||||||
|
pub description: String,
|
||||||
|
pub source_id: Option<String>,
|
||||||
|
pub source_name: Option<String>,
|
||||||
|
pub destination_id: Option<String>,
|
||||||
|
pub destination_name: Option<String>,
|
||||||
|
pub currency_code: Option<String>,
|
||||||
|
pub foreign_amount: Option<String>,
|
||||||
|
pub foreign_currency_code: Option<String>,
|
||||||
|
pub external_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionStore {
|
||||||
|
pub transactions: Vec<TransactionSplitStore>,
|
||||||
|
pub apply_rules: Option<bool>,
|
||||||
|
pub fire_webhooks: Option<bool>,
|
||||||
|
pub error_if_duplicate_hash: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionUpdate {
|
||||||
|
pub transactions: Vec<TransactionSplitUpdate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TransactionSplitUpdate {
|
||||||
|
pub external_id: Option<String>,
|
||||||
|
}
|
||||||
62
firefly-client/tests/client_test.rs
Normal file
62
firefly-client/tests/client_test.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use firefly_client::client::FireflyClient;
|
||||||
|
use firefly_client::models::{TransactionStore, TransactionSplitStore};
|
||||||
|
use wiremock::matchers::{method, path, header};
|
||||||
|
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_accounts() {
|
||||||
|
let mock_server = MockServer::start().await;
|
||||||
|
let fixture = fs::read_to_string("tests/fixtures/ff_accounts.json").unwrap();
|
||||||
|
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.and(path("/api/v1/search/accounts"))
|
||||||
|
.and(header("Authorization", "Bearer my-token"))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_string(fixture))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let client = FireflyClient::new(&mock_server.uri(), "my-token").unwrap();
|
||||||
|
let accounts = client.search_accounts("NL01").await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(accounts.data.len(), 1);
|
||||||
|
assert_eq!(accounts.data[0].attributes.name, "Checking Account");
|
||||||
|
assert_eq!(accounts.data[0].attributes.iban.as_deref(), Some("NL01BANK0123456789"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_store_transaction() {
|
||||||
|
let mock_server = MockServer::start().await;
|
||||||
|
|
||||||
|
Mock::given(method("POST"))
|
||||||
|
.and(path("/api/v1/transactions"))
|
||||||
|
.and(header("Authorization", "Bearer my-token"))
|
||||||
|
.respond_with(ResponseTemplate::new(200))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let client = FireflyClient::new(&mock_server.uri(), "my-token").unwrap();
|
||||||
|
|
||||||
|
let tx = TransactionStore {
|
||||||
|
transactions: vec![TransactionSplitStore {
|
||||||
|
transaction_type: "withdrawal".to_string(),
|
||||||
|
date: "2023-01-01".to_string(),
|
||||||
|
amount: "10.00".to_string(),
|
||||||
|
description: "Test".to_string(),
|
||||||
|
source_id: Some("1".to_string()),
|
||||||
|
destination_name: Some("Shop".to_string()),
|
||||||
|
currency_code: None,
|
||||||
|
foreign_amount: None,
|
||||||
|
foreign_currency_code: None,
|
||||||
|
external_id: None,
|
||||||
|
source_name: None,
|
||||||
|
destination_id: None,
|
||||||
|
}],
|
||||||
|
apply_rules: None,
|
||||||
|
fire_webhooks: None,
|
||||||
|
error_if_duplicate_hash: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = client.store_transaction(tx).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
22
firefly-client/tests/fixtures/ff_accounts.json
vendored
Normal file
22
firefly-client/tests/fixtures/ff_accounts.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"type": "accounts",
|
||||||
|
"id": "2",
|
||||||
|
"attributes": {
|
||||||
|
"name": "Checking Account",
|
||||||
|
"type": "asset",
|
||||||
|
"iban": "NL01BANK0123456789"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"pagination": {
|
||||||
|
"total": 1,
|
||||||
|
"count": 1,
|
||||||
|
"per_page": 20,
|
||||||
|
"current_page": 1,
|
||||||
|
"total_pages": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
firefly-iii-api/.gitignore
vendored
3
firefly-iii-api/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
/target/
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
||||||
8
firefly-iii-api/.idea/.gitignore
generated
vendored
8
firefly-iii-api/.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
20
firefly-iii-api/.idea/misc.xml
generated
20
firefly-iii-api/.idea/misc.xml
generated
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectInspectionProfilesVisibleTreeState">
|
|
||||||
<entry key="Project Default">
|
|
||||||
<profile-state>
|
|
||||||
<expanded-state>
|
|
||||||
<State />
|
|
||||||
<State>
|
|
||||||
<id>SQL</id>
|
|
||||||
</State>
|
|
||||||
</expanded-state>
|
|
||||||
<selected-state>
|
|
||||||
<State>
|
|
||||||
<id>CSS</id>
|
|
||||||
</State>
|
|
||||||
</selected-state>
|
|
||||||
</profile-state>
|
|
||||||
</entry>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
8
firefly-iii-api/.idea/modules.xml
generated
8
firefly-iii-api/.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/rust.iml" filepath="$PROJECT_DIR$/.idea/rust.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
11
firefly-iii-api/.idea/rust.iml
generated
11
firefly-iii-api/.idea/rust.iml
generated
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="EMPTY_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# OpenAPI Generator Ignore
|
|
||||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
|
||||||
|
|
||||||
# Use this file to prevent files from being overwritten by the generator.
|
|
||||||
# The patterns follow closely to .gitignore or .dockerignore.
|
|
||||||
|
|
||||||
# As an example, the C# client generator defines ApiClient.cs.
|
|
||||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
|
||||||
#ApiClient.cs
|
|
||||||
|
|
||||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
|
||||||
#foo/*/qux
|
|
||||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
|
||||||
|
|
||||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
|
||||||
#foo/**/qux
|
|
||||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
|
||||||
|
|
||||||
# You can also negate patterns with an exclamation (!).
|
|
||||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
|
||||||
#docs/*.md
|
|
||||||
# Then explicitly reverse the ignore rule for a single file:
|
|
||||||
#!docs/README.md
|
|
||||||
@@ -1,484 +0,0 @@
|
|||||||
.gitignore
|
|
||||||
.openapi-generator-ignore
|
|
||||||
.travis.yml
|
|
||||||
Cargo.toml
|
|
||||||
README.md
|
|
||||||
docs/AboutApi.md
|
|
||||||
docs/Account.md
|
|
||||||
docs/AccountArray.md
|
|
||||||
docs/AccountRead.md
|
|
||||||
docs/AccountRoleProperty.md
|
|
||||||
docs/AccountSearchFieldFilter.md
|
|
||||||
docs/AccountSingle.md
|
|
||||||
docs/AccountStore.md
|
|
||||||
docs/AccountTypeFilter.md
|
|
||||||
docs/AccountTypeProperty.md
|
|
||||||
docs/AccountUpdate.md
|
|
||||||
docs/AccountsApi.md
|
|
||||||
docs/AttachableType.md
|
|
||||||
docs/Attachment.md
|
|
||||||
docs/AttachmentArray.md
|
|
||||||
docs/AttachmentRead.md
|
|
||||||
docs/AttachmentSingle.md
|
|
||||||
docs/AttachmentStore.md
|
|
||||||
docs/AttachmentUpdate.md
|
|
||||||
docs/AttachmentsApi.md
|
|
||||||
docs/AutoBudgetPeriod.md
|
|
||||||
docs/AutoBudgetType.md
|
|
||||||
docs/AutocompleteAccount.md
|
|
||||||
docs/AutocompleteApi.md
|
|
||||||
docs/AutocompleteBill.md
|
|
||||||
docs/AutocompleteBudget.md
|
|
||||||
docs/AutocompleteCategory.md
|
|
||||||
docs/AutocompleteCurrency.md
|
|
||||||
docs/AutocompleteCurrencyCode.md
|
|
||||||
docs/AutocompleteObjectGroup.md
|
|
||||||
docs/AutocompletePiggy.md
|
|
||||||
docs/AutocompletePiggyBalance.md
|
|
||||||
docs/AutocompleteRecurrence.md
|
|
||||||
docs/AutocompleteRule.md
|
|
||||||
docs/AutocompleteRuleGroup.md
|
|
||||||
docs/AutocompleteTag.md
|
|
||||||
docs/AutocompleteTransaction.md
|
|
||||||
docs/AutocompleteTransactionId.md
|
|
||||||
docs/AutocompleteTransactionType.md
|
|
||||||
docs/AvailableBudget.md
|
|
||||||
docs/AvailableBudgetArray.md
|
|
||||||
docs/AvailableBudgetRead.md
|
|
||||||
docs/AvailableBudgetSingle.md
|
|
||||||
docs/AvailableBudgetsApi.md
|
|
||||||
docs/BadRequestResponse.md
|
|
||||||
docs/BasicSummaryEntry.md
|
|
||||||
docs/Bill.md
|
|
||||||
docs/BillArray.md
|
|
||||||
docs/BillPaidDatesInner.md
|
|
||||||
docs/BillRead.md
|
|
||||||
docs/BillRepeatFrequency.md
|
|
||||||
docs/BillSingle.md
|
|
||||||
docs/BillStore.md
|
|
||||||
docs/BillUpdate.md
|
|
||||||
docs/BillsApi.md
|
|
||||||
docs/Budget.md
|
|
||||||
docs/BudgetArray.md
|
|
||||||
docs/BudgetLimit.md
|
|
||||||
docs/BudgetLimitArray.md
|
|
||||||
docs/BudgetLimitRead.md
|
|
||||||
docs/BudgetLimitSingle.md
|
|
||||||
docs/BudgetLimitStore.md
|
|
||||||
docs/BudgetRead.md
|
|
||||||
docs/BudgetSingle.md
|
|
||||||
docs/BudgetSpent.md
|
|
||||||
docs/BudgetStore.md
|
|
||||||
docs/BudgetUpdate.md
|
|
||||||
docs/BudgetsApi.md
|
|
||||||
docs/CategoriesApi.md
|
|
||||||
docs/Category.md
|
|
||||||
docs/CategoryArray.md
|
|
||||||
docs/CategoryEarned.md
|
|
||||||
docs/CategoryRead.md
|
|
||||||
docs/CategorySingle.md
|
|
||||||
docs/CategorySpent.md
|
|
||||||
docs/CategoryUpdate.md
|
|
||||||
docs/ChartDataPoint.md
|
|
||||||
docs/ChartDataSet.md
|
|
||||||
docs/ChartsApi.md
|
|
||||||
docs/ConfigValueFilter.md
|
|
||||||
docs/ConfigValueUpdateFilter.md
|
|
||||||
docs/Configuration.md
|
|
||||||
docs/ConfigurationApi.md
|
|
||||||
docs/ConfigurationSingle.md
|
|
||||||
docs/ConfigurationUpdate.md
|
|
||||||
docs/CreditCardTypeProperty.md
|
|
||||||
docs/CronResult.md
|
|
||||||
docs/CronResultRow.md
|
|
||||||
docs/CurrenciesApi.md
|
|
||||||
docs/Currency.md
|
|
||||||
docs/CurrencyArray.md
|
|
||||||
docs/CurrencyRead.md
|
|
||||||
docs/CurrencySingle.md
|
|
||||||
docs/CurrencyStore.md
|
|
||||||
docs/CurrencyUpdate.md
|
|
||||||
docs/DataApi.md
|
|
||||||
docs/DataDestroyObject.md
|
|
||||||
docs/ExportFileFilter.md
|
|
||||||
docs/InsightApi.md
|
|
||||||
docs/InsightGroupEntry.md
|
|
||||||
docs/InsightTotalEntry.md
|
|
||||||
docs/InsightTransferEntry.md
|
|
||||||
docs/InterestPeriodProperty.md
|
|
||||||
docs/InternalExceptionResponse.md
|
|
||||||
docs/LiabilityDirectionProperty.md
|
|
||||||
docs/LiabilityTypeProperty.md
|
|
||||||
docs/LinkType.md
|
|
||||||
docs/LinkTypeArray.md
|
|
||||||
docs/LinkTypeRead.md
|
|
||||||
docs/LinkTypeSingle.md
|
|
||||||
docs/LinkTypeUpdate.md
|
|
||||||
docs/LinksApi.md
|
|
||||||
docs/Meta.md
|
|
||||||
docs/MetaPagination.md
|
|
||||||
docs/NotFoundResponse.md
|
|
||||||
docs/ObjectGroup.md
|
|
||||||
docs/ObjectGroupArray.md
|
|
||||||
docs/ObjectGroupRead.md
|
|
||||||
docs/ObjectGroupSingle.md
|
|
||||||
docs/ObjectGroupUpdate.md
|
|
||||||
docs/ObjectGroupsApi.md
|
|
||||||
docs/ObjectLink.md
|
|
||||||
docs/ObjectLink0.md
|
|
||||||
docs/PageLink.md
|
|
||||||
docs/PiggyBank.md
|
|
||||||
docs/PiggyBankArray.md
|
|
||||||
docs/PiggyBankEvent.md
|
|
||||||
docs/PiggyBankEventArray.md
|
|
||||||
docs/PiggyBankEventRead.md
|
|
||||||
docs/PiggyBankRead.md
|
|
||||||
docs/PiggyBankSingle.md
|
|
||||||
docs/PiggyBankStore.md
|
|
||||||
docs/PiggyBankUpdate.md
|
|
||||||
docs/PiggyBanksApi.md
|
|
||||||
docs/PolymorphicProperty.md
|
|
||||||
docs/Preference.md
|
|
||||||
docs/PreferenceArray.md
|
|
||||||
docs/PreferenceRead.md
|
|
||||||
docs/PreferenceSingle.md
|
|
||||||
docs/PreferenceUpdate.md
|
|
||||||
docs/PreferencesApi.md
|
|
||||||
docs/Recurrence.md
|
|
||||||
docs/RecurrenceArray.md
|
|
||||||
docs/RecurrenceRead.md
|
|
||||||
docs/RecurrenceRepetition.md
|
|
||||||
docs/RecurrenceRepetitionStore.md
|
|
||||||
docs/RecurrenceRepetitionType.md
|
|
||||||
docs/RecurrenceRepetitionUpdate.md
|
|
||||||
docs/RecurrenceSingle.md
|
|
||||||
docs/RecurrenceStore.md
|
|
||||||
docs/RecurrenceTransaction.md
|
|
||||||
docs/RecurrenceTransactionStore.md
|
|
||||||
docs/RecurrenceTransactionType.md
|
|
||||||
docs/RecurrenceTransactionUpdate.md
|
|
||||||
docs/RecurrenceUpdate.md
|
|
||||||
docs/RecurrencesApi.md
|
|
||||||
docs/Rule.md
|
|
||||||
docs/RuleAction.md
|
|
||||||
docs/RuleActionKeyword.md
|
|
||||||
docs/RuleActionStore.md
|
|
||||||
docs/RuleActionUpdate.md
|
|
||||||
docs/RuleArray.md
|
|
||||||
docs/RuleGroup.md
|
|
||||||
docs/RuleGroupArray.md
|
|
||||||
docs/RuleGroupRead.md
|
|
||||||
docs/RuleGroupSingle.md
|
|
||||||
docs/RuleGroupStore.md
|
|
||||||
docs/RuleGroupUpdate.md
|
|
||||||
docs/RuleGroupsApi.md
|
|
||||||
docs/RuleRead.md
|
|
||||||
docs/RuleSingle.md
|
|
||||||
docs/RuleStore.md
|
|
||||||
docs/RuleTrigger.md
|
|
||||||
docs/RuleTriggerKeyword.md
|
|
||||||
docs/RuleTriggerStore.md
|
|
||||||
docs/RuleTriggerType.md
|
|
||||||
docs/RuleTriggerUpdate.md
|
|
||||||
docs/RuleUpdate.md
|
|
||||||
docs/RulesApi.md
|
|
||||||
docs/SearchApi.md
|
|
||||||
docs/ShortAccountTypeProperty.md
|
|
||||||
docs/SummaryApi.md
|
|
||||||
docs/SystemInfo.md
|
|
||||||
docs/SystemInfoData.md
|
|
||||||
docs/TagArray.md
|
|
||||||
docs/TagModel.md
|
|
||||||
docs/TagModelStore.md
|
|
||||||
docs/TagModelUpdate.md
|
|
||||||
docs/TagRead.md
|
|
||||||
docs/TagSingle.md
|
|
||||||
docs/TagsApi.md
|
|
||||||
docs/Transaction.md
|
|
||||||
docs/TransactionArray.md
|
|
||||||
docs/TransactionLink.md
|
|
||||||
docs/TransactionLinkArray.md
|
|
||||||
docs/TransactionLinkRead.md
|
|
||||||
docs/TransactionLinkSingle.md
|
|
||||||
docs/TransactionLinkStore.md
|
|
||||||
docs/TransactionLinkUpdate.md
|
|
||||||
docs/TransactionRead.md
|
|
||||||
docs/TransactionSingle.md
|
|
||||||
docs/TransactionSplit.md
|
|
||||||
docs/TransactionSplitStore.md
|
|
||||||
docs/TransactionSplitUpdate.md
|
|
||||||
docs/TransactionStore.md
|
|
||||||
docs/TransactionTypeFilter.md
|
|
||||||
docs/TransactionTypeProperty.md
|
|
||||||
docs/TransactionUpdate.md
|
|
||||||
docs/TransactionsApi.md
|
|
||||||
docs/UnauthenticatedResponse.md
|
|
||||||
docs/User.md
|
|
||||||
docs/UserArray.md
|
|
||||||
docs/UserBlockedCodeProperty.md
|
|
||||||
docs/UserRead.md
|
|
||||||
docs/UserRoleProperty.md
|
|
||||||
docs/UserSingle.md
|
|
||||||
docs/UsersApi.md
|
|
||||||
docs/ValidationErrorResponse.md
|
|
||||||
docs/ValidationErrorResponseErrors.md
|
|
||||||
docs/Webhook.md
|
|
||||||
docs/WebhookArray.md
|
|
||||||
docs/WebhookAttempt.md
|
|
||||||
docs/WebhookAttemptArray.md
|
|
||||||
docs/WebhookAttemptRead.md
|
|
||||||
docs/WebhookAttemptSingle.md
|
|
||||||
docs/WebhookDelivery.md
|
|
||||||
docs/WebhookMessage.md
|
|
||||||
docs/WebhookMessageArray.md
|
|
||||||
docs/WebhookMessageRead.md
|
|
||||||
docs/WebhookMessageSingle.md
|
|
||||||
docs/WebhookRead.md
|
|
||||||
docs/WebhookResponse.md
|
|
||||||
docs/WebhookSingle.md
|
|
||||||
docs/WebhookStore.md
|
|
||||||
docs/WebhookTrigger.md
|
|
||||||
docs/WebhookUpdate.md
|
|
||||||
docs/WebhooksApi.md
|
|
||||||
git_push.sh
|
|
||||||
src/apis/about_api.rs
|
|
||||||
src/apis/accounts_api.rs
|
|
||||||
src/apis/attachments_api.rs
|
|
||||||
src/apis/autocomplete_api.rs
|
|
||||||
src/apis/available_budgets_api.rs
|
|
||||||
src/apis/bills_api.rs
|
|
||||||
src/apis/budgets_api.rs
|
|
||||||
src/apis/categories_api.rs
|
|
||||||
src/apis/charts_api.rs
|
|
||||||
src/apis/configuration.rs
|
|
||||||
src/apis/configuration_api.rs
|
|
||||||
src/apis/currencies_api.rs
|
|
||||||
src/apis/data_api.rs
|
|
||||||
src/apis/insight_api.rs
|
|
||||||
src/apis/links_api.rs
|
|
||||||
src/apis/mod.rs
|
|
||||||
src/apis/object_groups_api.rs
|
|
||||||
src/apis/piggy_banks_api.rs
|
|
||||||
src/apis/preferences_api.rs
|
|
||||||
src/apis/recurrences_api.rs
|
|
||||||
src/apis/rule_groups_api.rs
|
|
||||||
src/apis/rules_api.rs
|
|
||||||
src/apis/search_api.rs
|
|
||||||
src/apis/summary_api.rs
|
|
||||||
src/apis/tags_api.rs
|
|
||||||
src/apis/transactions_api.rs
|
|
||||||
src/apis/users_api.rs
|
|
||||||
src/apis/webhooks_api.rs
|
|
||||||
src/lib.rs
|
|
||||||
src/models/account.rs
|
|
||||||
src/models/account_array.rs
|
|
||||||
src/models/account_read.rs
|
|
||||||
src/models/account_role_property.rs
|
|
||||||
src/models/account_search_field_filter.rs
|
|
||||||
src/models/account_single.rs
|
|
||||||
src/models/account_store.rs
|
|
||||||
src/models/account_type_filter.rs
|
|
||||||
src/models/account_type_property.rs
|
|
||||||
src/models/account_update.rs
|
|
||||||
src/models/attachable_type.rs
|
|
||||||
src/models/attachment.rs
|
|
||||||
src/models/attachment_array.rs
|
|
||||||
src/models/attachment_read.rs
|
|
||||||
src/models/attachment_single.rs
|
|
||||||
src/models/attachment_store.rs
|
|
||||||
src/models/attachment_update.rs
|
|
||||||
src/models/auto_budget_period.rs
|
|
||||||
src/models/auto_budget_type.rs
|
|
||||||
src/models/autocomplete_account.rs
|
|
||||||
src/models/autocomplete_bill.rs
|
|
||||||
src/models/autocomplete_budget.rs
|
|
||||||
src/models/autocomplete_category.rs
|
|
||||||
src/models/autocomplete_currency.rs
|
|
||||||
src/models/autocomplete_currency_code.rs
|
|
||||||
src/models/autocomplete_object_group.rs
|
|
||||||
src/models/autocomplete_piggy.rs
|
|
||||||
src/models/autocomplete_piggy_balance.rs
|
|
||||||
src/models/autocomplete_recurrence.rs
|
|
||||||
src/models/autocomplete_rule.rs
|
|
||||||
src/models/autocomplete_rule_group.rs
|
|
||||||
src/models/autocomplete_tag.rs
|
|
||||||
src/models/autocomplete_transaction.rs
|
|
||||||
src/models/autocomplete_transaction_id.rs
|
|
||||||
src/models/autocomplete_transaction_type.rs
|
|
||||||
src/models/available_budget.rs
|
|
||||||
src/models/available_budget_array.rs
|
|
||||||
src/models/available_budget_read.rs
|
|
||||||
src/models/available_budget_single.rs
|
|
||||||
src/models/bad_request_response.rs
|
|
||||||
src/models/basic_summary_entry.rs
|
|
||||||
src/models/bill.rs
|
|
||||||
src/models/bill_array.rs
|
|
||||||
src/models/bill_paid_dates_inner.rs
|
|
||||||
src/models/bill_read.rs
|
|
||||||
src/models/bill_repeat_frequency.rs
|
|
||||||
src/models/bill_single.rs
|
|
||||||
src/models/bill_store.rs
|
|
||||||
src/models/bill_update.rs
|
|
||||||
src/models/budget.rs
|
|
||||||
src/models/budget_array.rs
|
|
||||||
src/models/budget_limit.rs
|
|
||||||
src/models/budget_limit_array.rs
|
|
||||||
src/models/budget_limit_read.rs
|
|
||||||
src/models/budget_limit_single.rs
|
|
||||||
src/models/budget_limit_store.rs
|
|
||||||
src/models/budget_read.rs
|
|
||||||
src/models/budget_single.rs
|
|
||||||
src/models/budget_spent.rs
|
|
||||||
src/models/budget_store.rs
|
|
||||||
src/models/budget_update.rs
|
|
||||||
src/models/category.rs
|
|
||||||
src/models/category_array.rs
|
|
||||||
src/models/category_earned.rs
|
|
||||||
src/models/category_read.rs
|
|
||||||
src/models/category_single.rs
|
|
||||||
src/models/category_spent.rs
|
|
||||||
src/models/category_update.rs
|
|
||||||
src/models/chart_data_point.rs
|
|
||||||
src/models/chart_data_set.rs
|
|
||||||
src/models/config_value_filter.rs
|
|
||||||
src/models/config_value_update_filter.rs
|
|
||||||
src/models/configuration.rs
|
|
||||||
src/models/configuration_single.rs
|
|
||||||
src/models/configuration_update.rs
|
|
||||||
src/models/credit_card_type_property.rs
|
|
||||||
src/models/cron_result.rs
|
|
||||||
src/models/cron_result_row.rs
|
|
||||||
src/models/currency.rs
|
|
||||||
src/models/currency_array.rs
|
|
||||||
src/models/currency_read.rs
|
|
||||||
src/models/currency_single.rs
|
|
||||||
src/models/currency_store.rs
|
|
||||||
src/models/currency_update.rs
|
|
||||||
src/models/data_destroy_object.rs
|
|
||||||
src/models/export_file_filter.rs
|
|
||||||
src/models/insight_group_entry.rs
|
|
||||||
src/models/insight_total_entry.rs
|
|
||||||
src/models/insight_transfer_entry.rs
|
|
||||||
src/models/interest_period_property.rs
|
|
||||||
src/models/internal_exception_response.rs
|
|
||||||
src/models/liability_direction_property.rs
|
|
||||||
src/models/liability_type_property.rs
|
|
||||||
src/models/link_type.rs
|
|
||||||
src/models/link_type_array.rs
|
|
||||||
src/models/link_type_read.rs
|
|
||||||
src/models/link_type_single.rs
|
|
||||||
src/models/link_type_update.rs
|
|
||||||
src/models/meta.rs
|
|
||||||
src/models/meta_pagination.rs
|
|
||||||
src/models/mod.rs
|
|
||||||
src/models/not_found_response.rs
|
|
||||||
src/models/object_group.rs
|
|
||||||
src/models/object_group_array.rs
|
|
||||||
src/models/object_group_read.rs
|
|
||||||
src/models/object_group_single.rs
|
|
||||||
src/models/object_group_update.rs
|
|
||||||
src/models/object_link.rs
|
|
||||||
src/models/object_link_0.rs
|
|
||||||
src/models/page_link.rs
|
|
||||||
src/models/piggy_bank.rs
|
|
||||||
src/models/piggy_bank_array.rs
|
|
||||||
src/models/piggy_bank_event.rs
|
|
||||||
src/models/piggy_bank_event_array.rs
|
|
||||||
src/models/piggy_bank_event_read.rs
|
|
||||||
src/models/piggy_bank_read.rs
|
|
||||||
src/models/piggy_bank_single.rs
|
|
||||||
src/models/piggy_bank_store.rs
|
|
||||||
src/models/piggy_bank_update.rs
|
|
||||||
src/models/polymorphic_property.rs
|
|
||||||
src/models/preference.rs
|
|
||||||
src/models/preference_array.rs
|
|
||||||
src/models/preference_read.rs
|
|
||||||
src/models/preference_single.rs
|
|
||||||
src/models/preference_update.rs
|
|
||||||
src/models/recurrence.rs
|
|
||||||
src/models/recurrence_array.rs
|
|
||||||
src/models/recurrence_read.rs
|
|
||||||
src/models/recurrence_repetition.rs
|
|
||||||
src/models/recurrence_repetition_store.rs
|
|
||||||
src/models/recurrence_repetition_type.rs
|
|
||||||
src/models/recurrence_repetition_update.rs
|
|
||||||
src/models/recurrence_single.rs
|
|
||||||
src/models/recurrence_store.rs
|
|
||||||
src/models/recurrence_transaction.rs
|
|
||||||
src/models/recurrence_transaction_store.rs
|
|
||||||
src/models/recurrence_transaction_type.rs
|
|
||||||
src/models/recurrence_transaction_update.rs
|
|
||||||
src/models/recurrence_update.rs
|
|
||||||
src/models/rule.rs
|
|
||||||
src/models/rule_action.rs
|
|
||||||
src/models/rule_action_keyword.rs
|
|
||||||
src/models/rule_action_store.rs
|
|
||||||
src/models/rule_action_update.rs
|
|
||||||
src/models/rule_array.rs
|
|
||||||
src/models/rule_group.rs
|
|
||||||
src/models/rule_group_array.rs
|
|
||||||
src/models/rule_group_read.rs
|
|
||||||
src/models/rule_group_single.rs
|
|
||||||
src/models/rule_group_store.rs
|
|
||||||
src/models/rule_group_update.rs
|
|
||||||
src/models/rule_read.rs
|
|
||||||
src/models/rule_single.rs
|
|
||||||
src/models/rule_store.rs
|
|
||||||
src/models/rule_trigger.rs
|
|
||||||
src/models/rule_trigger_keyword.rs
|
|
||||||
src/models/rule_trigger_store.rs
|
|
||||||
src/models/rule_trigger_type.rs
|
|
||||||
src/models/rule_trigger_update.rs
|
|
||||||
src/models/rule_update.rs
|
|
||||||
src/models/short_account_type_property.rs
|
|
||||||
src/models/system_info.rs
|
|
||||||
src/models/system_info_data.rs
|
|
||||||
src/models/tag_array.rs
|
|
||||||
src/models/tag_model.rs
|
|
||||||
src/models/tag_model_store.rs
|
|
||||||
src/models/tag_model_update.rs
|
|
||||||
src/models/tag_read.rs
|
|
||||||
src/models/tag_single.rs
|
|
||||||
src/models/transaction.rs
|
|
||||||
src/models/transaction_array.rs
|
|
||||||
src/models/transaction_link.rs
|
|
||||||
src/models/transaction_link_array.rs
|
|
||||||
src/models/transaction_link_read.rs
|
|
||||||
src/models/transaction_link_single.rs
|
|
||||||
src/models/transaction_link_store.rs
|
|
||||||
src/models/transaction_link_update.rs
|
|
||||||
src/models/transaction_read.rs
|
|
||||||
src/models/transaction_single.rs
|
|
||||||
src/models/transaction_split.rs
|
|
||||||
src/models/transaction_split_store.rs
|
|
||||||
src/models/transaction_split_update.rs
|
|
||||||
src/models/transaction_store.rs
|
|
||||||
src/models/transaction_type_filter.rs
|
|
||||||
src/models/transaction_type_property.rs
|
|
||||||
src/models/transaction_update.rs
|
|
||||||
src/models/unauthenticated_response.rs
|
|
||||||
src/models/user.rs
|
|
||||||
src/models/user_array.rs
|
|
||||||
src/models/user_blocked_code_property.rs
|
|
||||||
src/models/user_read.rs
|
|
||||||
src/models/user_role_property.rs
|
|
||||||
src/models/user_single.rs
|
|
||||||
src/models/validation_error_response.rs
|
|
||||||
src/models/validation_error_response_errors.rs
|
|
||||||
src/models/webhook.rs
|
|
||||||
src/models/webhook_array.rs
|
|
||||||
src/models/webhook_attempt.rs
|
|
||||||
src/models/webhook_attempt_array.rs
|
|
||||||
src/models/webhook_attempt_read.rs
|
|
||||||
src/models/webhook_attempt_single.rs
|
|
||||||
src/models/webhook_delivery.rs
|
|
||||||
src/models/webhook_message.rs
|
|
||||||
src/models/webhook_message_array.rs
|
|
||||||
src/models/webhook_message_read.rs
|
|
||||||
src/models/webhook_message_single.rs
|
|
||||||
src/models/webhook_read.rs
|
|
||||||
src/models/webhook_response.rs
|
|
||||||
src/models/webhook_single.rs
|
|
||||||
src/models/webhook_store.rs
|
|
||||||
src/models/webhook_trigger.rs
|
|
||||||
src/models/webhook_update.rs
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
7.9.0-SNAPSHOT
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
language: rust
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "firefly-iii-api"
|
|
||||||
version = "2.1.0"
|
|
||||||
authors = ["james@firefly-iii.org"]
|
|
||||||
description = "This is the documentation of the Firefly III API. You can find accompanying documentation on the website of Firefly III itself (see below). Please report any bugs or issues. You may use the \"Authorize\" button to try the API below. This file was last generated on 2024-09-10T05:07:57+00:00 Please keep in mind that the demo site does not accept requests from curl, colly, wget, etc. You must use a browser or a tool like Postman to make requests. Too many script kiddies out there, sorry about that. "
|
|
||||||
license = "AGPLv3"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
|
||||||
serde_with = { version = "^3.8", default-features = false, features = ["base64", "std", "macros"] }
|
|
||||||
serde_json = "^1.0"
|
|
||||||
serde_repr = "^0.1"
|
|
||||||
url = "^2.5"
|
|
||||||
uuid = { version = "^1.8", features = ["serde", "v4"] }
|
|
||||||
reqwest = { version = "^0.12", features = ["json", "multipart"] }
|
|
||||||
@@ -1,467 +0,0 @@
|
|||||||
# Rust API client for openapi
|
|
||||||
|
|
||||||
This is the documentation of the Firefly III API. You can find accompanying documentation on the website of Firefly III itself (see below). Please report any bugs or issues. You may use the \"Authorize\" button to try the API below. This file was last generated on 2024-09-10T05:07:57+00:00
|
|
||||||
|
|
||||||
Please keep in mind that the demo site does not accept requests from curl, colly, wget, etc. You must use a browser or a tool like Postman to make requests. Too many script kiddies out there, sorry about that.
|
|
||||||
|
|
||||||
|
|
||||||
For more information, please visit [https://firefly-iii.org](https://firefly-iii.org)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client.
|
|
||||||
|
|
||||||
- API version: 2.1.0
|
|
||||||
- Package version: 2.1.0
|
|
||||||
- Generator version: 7.9.0-SNAPSHOT
|
|
||||||
- Build package: `org.openapitools.codegen.languages.RustClientCodegen`
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Put the package under your project folder in a directory named `openapi` and add the following to `Cargo.toml` under `[dependencies]`:
|
|
||||||
|
|
||||||
```
|
|
||||||
openapi = { path = "./openapi" }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation for API Endpoints
|
|
||||||
|
|
||||||
All URIs are relative to *https://demo.firefly-iii.org/api*
|
|
||||||
|
|
||||||
Class | Method | HTTP request | Description
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
*AboutApi* | [**get_about**](docs/AboutApi.md#get_about) | **GET** /v1/about | System information end point.
|
|
||||||
*AboutApi* | [**get_cron**](docs/AboutApi.md#get_cron) | **GET** /v1/cron/{cliToken} | Cron job endpoint
|
|
||||||
*AboutApi* | [**get_current_user**](docs/AboutApi.md#get_current_user) | **GET** /v1/about/user | Currently authenticated user endpoint.
|
|
||||||
*AccountsApi* | [**delete_account**](docs/AccountsApi.md#delete_account) | **DELETE** /v1/accounts/{id} | Permanently delete account.
|
|
||||||
*AccountsApi* | [**get_account**](docs/AccountsApi.md#get_account) | **GET** /v1/accounts/{id} | Get single account.
|
|
||||||
*AccountsApi* | [**list_account**](docs/AccountsApi.md#list_account) | **GET** /v1/accounts | List all accounts.
|
|
||||||
*AccountsApi* | [**list_attachment_by_account**](docs/AccountsApi.md#list_attachment_by_account) | **GET** /v1/accounts/{id}/attachments | Lists all attachments.
|
|
||||||
*AccountsApi* | [**list_piggy_bank_by_account**](docs/AccountsApi.md#list_piggy_bank_by_account) | **GET** /v1/accounts/{id}/piggy-banks | List all piggy banks related to the account.
|
|
||||||
*AccountsApi* | [**list_transaction_by_account**](docs/AccountsApi.md#list_transaction_by_account) | **GET** /v1/accounts/{id}/transactions | List all transactions related to the account.
|
|
||||||
*AccountsApi* | [**store_account**](docs/AccountsApi.md#store_account) | **POST** /v1/accounts | Create new account.
|
|
||||||
*AccountsApi* | [**update_account**](docs/AccountsApi.md#update_account) | **PUT** /v1/accounts/{id} | Update existing account.
|
|
||||||
*AttachmentsApi* | [**delete_attachment**](docs/AttachmentsApi.md#delete_attachment) | **DELETE** /v1/attachments/{id} | Delete an attachment.
|
|
||||||
*AttachmentsApi* | [**download_attachment**](docs/AttachmentsApi.md#download_attachment) | **GET** /v1/attachments/{id}/download | Download a single attachment.
|
|
||||||
*AttachmentsApi* | [**get_attachment**](docs/AttachmentsApi.md#get_attachment) | **GET** /v1/attachments/{id} | Get a single attachment.
|
|
||||||
*AttachmentsApi* | [**list_attachment**](docs/AttachmentsApi.md#list_attachment) | **GET** /v1/attachments | List all attachments.
|
|
||||||
*AttachmentsApi* | [**store_attachment**](docs/AttachmentsApi.md#store_attachment) | **POST** /v1/attachments | Store a new attachment.
|
|
||||||
*AttachmentsApi* | [**update_attachment**](docs/AttachmentsApi.md#update_attachment) | **PUT** /v1/attachments/{id} | Update existing attachment.
|
|
||||||
*AttachmentsApi* | [**upload_attachment**](docs/AttachmentsApi.md#upload_attachment) | **POST** /v1/attachments/{id}/upload | Upload an attachment.
|
|
||||||
*AutocompleteApi* | [**get_accounts_ac**](docs/AutocompleteApi.md#get_accounts_ac) | **GET** /v1/autocomplete/accounts | Returns all accounts of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_bills_ac**](docs/AutocompleteApi.md#get_bills_ac) | **GET** /v1/autocomplete/bills | Returns all bills of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_budgets_ac**](docs/AutocompleteApi.md#get_budgets_ac) | **GET** /v1/autocomplete/budgets | Returns all budgets of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_categories_ac**](docs/AutocompleteApi.md#get_categories_ac) | **GET** /v1/autocomplete/categories | Returns all categories of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_currencies_ac**](docs/AutocompleteApi.md#get_currencies_ac) | **GET** /v1/autocomplete/currencies | Returns all currencies of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_currencies_code_ac**](docs/AutocompleteApi.md#get_currencies_code_ac) | **GET** /v1/autocomplete/currencies-with-code | Returns all currencies of the user returned in a basic auto-complete array. This endpoint is DEPRECATED and I suggest you DO NOT use it.
|
|
||||||
*AutocompleteApi* | [**get_object_groups_ac**](docs/AutocompleteApi.md#get_object_groups_ac) | **GET** /v1/autocomplete/object-groups | Returns all object groups of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_piggies_ac**](docs/AutocompleteApi.md#get_piggies_ac) | **GET** /v1/autocomplete/piggy-banks | Returns all piggy banks of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_piggies_balance_ac**](docs/AutocompleteApi.md#get_piggies_balance_ac) | **GET** /v1/autocomplete/piggy-banks-with-balance | Returns all piggy banks of the user returned in a basic auto-complete array complemented with balance information.
|
|
||||||
*AutocompleteApi* | [**get_recurring_ac**](docs/AutocompleteApi.md#get_recurring_ac) | **GET** /v1/autocomplete/recurring | Returns all recurring transactions of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_rule_groups_ac**](docs/AutocompleteApi.md#get_rule_groups_ac) | **GET** /v1/autocomplete/rule-groups | Returns all rule groups of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_rules_ac**](docs/AutocompleteApi.md#get_rules_ac) | **GET** /v1/autocomplete/rules | Returns all rules of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_tag_ac**](docs/AutocompleteApi.md#get_tag_ac) | **GET** /v1/autocomplete/tags | Returns all tags of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_transaction_types_ac**](docs/AutocompleteApi.md#get_transaction_types_ac) | **GET** /v1/autocomplete/transaction-types | Returns all transaction types returned in a basic auto-complete array. English only.
|
|
||||||
*AutocompleteApi* | [**get_transactions_ac**](docs/AutocompleteApi.md#get_transactions_ac) | **GET** /v1/autocomplete/transactions | Returns all transaction descriptions of the user returned in a basic auto-complete array.
|
|
||||||
*AutocompleteApi* | [**get_transactions_idac**](docs/AutocompleteApi.md#get_transactions_idac) | **GET** /v1/autocomplete/transactions-with-id | Returns all transactions, complemented with their ID, of the user returned in a basic auto-complete array. This endpoint is DEPRECATED and I suggest you DO NOT use it.
|
|
||||||
*AvailableBudgetsApi* | [**get_available_budget**](docs/AvailableBudgetsApi.md#get_available_budget) | **GET** /v1/available-budgets/{id} | Get a single available budget.
|
|
||||||
*AvailableBudgetsApi* | [**list_available_budget**](docs/AvailableBudgetsApi.md#list_available_budget) | **GET** /v1/available-budgets | List all available budget amounts.
|
|
||||||
*BillsApi* | [**delete_bill**](docs/BillsApi.md#delete_bill) | **DELETE** /v1/bills/{id} | Delete a bill.
|
|
||||||
*BillsApi* | [**get_bill**](docs/BillsApi.md#get_bill) | **GET** /v1/bills/{id} | Get a single bill.
|
|
||||||
*BillsApi* | [**list_attachment_by_bill**](docs/BillsApi.md#list_attachment_by_bill) | **GET** /v1/bills/{id}/attachments | List all attachments uploaded to the bill.
|
|
||||||
*BillsApi* | [**list_bill**](docs/BillsApi.md#list_bill) | **GET** /v1/bills | List all bills.
|
|
||||||
*BillsApi* | [**list_rule_by_bill**](docs/BillsApi.md#list_rule_by_bill) | **GET** /v1/bills/{id}/rules | List all rules associated with the bill.
|
|
||||||
*BillsApi* | [**list_transaction_by_bill**](docs/BillsApi.md#list_transaction_by_bill) | **GET** /v1/bills/{id}/transactions | List all transactions associated with the bill.
|
|
||||||
*BillsApi* | [**store_bill**](docs/BillsApi.md#store_bill) | **POST** /v1/bills | Store a new bill
|
|
||||||
*BillsApi* | [**update_bill**](docs/BillsApi.md#update_bill) | **PUT** /v1/bills/{id} | Update existing bill.
|
|
||||||
*BudgetsApi* | [**delete_budget**](docs/BudgetsApi.md#delete_budget) | **DELETE** /v1/budgets/{id} | Delete a budget.
|
|
||||||
*BudgetsApi* | [**delete_budget_limit**](docs/BudgetsApi.md#delete_budget_limit) | **DELETE** /v1/budgets/{id}/limits/{limitId} | Delete a budget limit.
|
|
||||||
*BudgetsApi* | [**get_budget**](docs/BudgetsApi.md#get_budget) | **GET** /v1/budgets/{id} | Get a single budget.
|
|
||||||
*BudgetsApi* | [**get_budget_limit**](docs/BudgetsApi.md#get_budget_limit) | **GET** /v1/budgets/{id}/limits/{limitId} | Get single budget limit.
|
|
||||||
*BudgetsApi* | [**list_attachment_by_budget**](docs/BudgetsApi.md#list_attachment_by_budget) | **GET** /v1/budgets/{id}/attachments | Lists all attachments of a budget.
|
|
||||||
*BudgetsApi* | [**list_budget**](docs/BudgetsApi.md#list_budget) | **GET** /v1/budgets | List all budgets.
|
|
||||||
*BudgetsApi* | [**list_budget_limit**](docs/BudgetsApi.md#list_budget_limit) | **GET** /v1/budget-limits | Get list of budget limits by date
|
|
||||||
*BudgetsApi* | [**list_budget_limit_by_budget**](docs/BudgetsApi.md#list_budget_limit_by_budget) | **GET** /v1/budgets/{id}/limits | Get all limits for a budget.
|
|
||||||
*BudgetsApi* | [**list_transaction_by_budget**](docs/BudgetsApi.md#list_transaction_by_budget) | **GET** /v1/budgets/{id}/transactions | All transactions to a budget.
|
|
||||||
*BudgetsApi* | [**list_transaction_by_budget_limit**](docs/BudgetsApi.md#list_transaction_by_budget_limit) | **GET** /v1/budgets/{id}/limits/{limitId}/transactions | List all transactions by a budget limit ID.
|
|
||||||
*BudgetsApi* | [**store_budget**](docs/BudgetsApi.md#store_budget) | **POST** /v1/budgets | Store a new budget
|
|
||||||
*BudgetsApi* | [**store_budget_limit**](docs/BudgetsApi.md#store_budget_limit) | **POST** /v1/budgets/{id}/limits | Store new budget limit.
|
|
||||||
*BudgetsApi* | [**update_budget**](docs/BudgetsApi.md#update_budget) | **PUT** /v1/budgets/{id} | Update existing budget.
|
|
||||||
*BudgetsApi* | [**update_budget_limit**](docs/BudgetsApi.md#update_budget_limit) | **PUT** /v1/budgets/{id}/limits/{limitId} | Update existing budget limit.
|
|
||||||
*CategoriesApi* | [**delete_category**](docs/CategoriesApi.md#delete_category) | **DELETE** /v1/categories/{id} | Delete a category.
|
|
||||||
*CategoriesApi* | [**get_category**](docs/CategoriesApi.md#get_category) | **GET** /v1/categories/{id} | Get a single category.
|
|
||||||
*CategoriesApi* | [**list_attachment_by_category**](docs/CategoriesApi.md#list_attachment_by_category) | **GET** /v1/categories/{id}/attachments | Lists all attachments.
|
|
||||||
*CategoriesApi* | [**list_category**](docs/CategoriesApi.md#list_category) | **GET** /v1/categories | List all categories.
|
|
||||||
*CategoriesApi* | [**list_transaction_by_category**](docs/CategoriesApi.md#list_transaction_by_category) | **GET** /v1/categories/{id}/transactions | List all transactions in a category.
|
|
||||||
*CategoriesApi* | [**store_category**](docs/CategoriesApi.md#store_category) | **POST** /v1/categories | Store a new category
|
|
||||||
*CategoriesApi* | [**update_category**](docs/CategoriesApi.md#update_category) | **PUT** /v1/categories/{id} | Update existing category.
|
|
||||||
*ChartsApi* | [**get_chart_account_overview**](docs/ChartsApi.md#get_chart_account_overview) | **GET** /v1/chart/account/overview | Dashboard chart with asset account balance information.
|
|
||||||
*ConfigurationApi* | [**get_configuration**](docs/ConfigurationApi.md#get_configuration) | **GET** /v1/configuration | Get Firefly III system configuration values.
|
|
||||||
*ConfigurationApi* | [**get_single_configuration**](docs/ConfigurationApi.md#get_single_configuration) | **GET** /v1/configuration/{name} | Get a single Firefly III system configuration value
|
|
||||||
*ConfigurationApi* | [**set_configuration**](docs/ConfigurationApi.md#set_configuration) | **PUT** /v1/configuration/{name} | Update configuration value
|
|
||||||
*CurrenciesApi* | [**default_currency**](docs/CurrenciesApi.md#default_currency) | **POST** /v1/currencies/{code}/default | Make currency default currency.
|
|
||||||
*CurrenciesApi* | [**delete_currency**](docs/CurrenciesApi.md#delete_currency) | **DELETE** /v1/currencies/{code} | Delete a currency.
|
|
||||||
*CurrenciesApi* | [**disable_currency**](docs/CurrenciesApi.md#disable_currency) | **POST** /v1/currencies/{code}/disable | Disable a currency.
|
|
||||||
*CurrenciesApi* | [**enable_currency**](docs/CurrenciesApi.md#enable_currency) | **POST** /v1/currencies/{code}/enable | Enable a single currency.
|
|
||||||
*CurrenciesApi* | [**get_currency**](docs/CurrenciesApi.md#get_currency) | **GET** /v1/currencies/{code} | Get a single currency.
|
|
||||||
*CurrenciesApi* | [**get_default_currency**](docs/CurrenciesApi.md#get_default_currency) | **GET** /v1/currencies/default | Get the user's default currency.
|
|
||||||
*CurrenciesApi* | [**list_account_by_currency**](docs/CurrenciesApi.md#list_account_by_currency) | **GET** /v1/currencies/{code}/accounts | List all accounts with this currency.
|
|
||||||
*CurrenciesApi* | [**list_available_budget_by_currency**](docs/CurrenciesApi.md#list_available_budget_by_currency) | **GET** /v1/currencies/{code}/available-budgets | List all available budgets with this currency.
|
|
||||||
*CurrenciesApi* | [**list_bill_by_currency**](docs/CurrenciesApi.md#list_bill_by_currency) | **GET** /v1/currencies/{code}/bills | List all bills with this currency.
|
|
||||||
*CurrenciesApi* | [**list_budget_limit_by_currency**](docs/CurrenciesApi.md#list_budget_limit_by_currency) | **GET** /v1/currencies/{code}/budget_limits | List all budget limits with this currency
|
|
||||||
*CurrenciesApi* | [**list_currency**](docs/CurrenciesApi.md#list_currency) | **GET** /v1/currencies | List all currencies.
|
|
||||||
*CurrenciesApi* | [**list_recurrence_by_currency**](docs/CurrenciesApi.md#list_recurrence_by_currency) | **GET** /v1/currencies/{code}/recurrences | List all recurring transactions with this currency.
|
|
||||||
*CurrenciesApi* | [**list_rule_by_currency**](docs/CurrenciesApi.md#list_rule_by_currency) | **GET** /v1/currencies/{code}/rules | List all rules with this currency.
|
|
||||||
*CurrenciesApi* | [**list_transaction_by_currency**](docs/CurrenciesApi.md#list_transaction_by_currency) | **GET** /v1/currencies/{code}/transactions | List all transactions with this currency.
|
|
||||||
*CurrenciesApi* | [**store_currency**](docs/CurrenciesApi.md#store_currency) | **POST** /v1/currencies | Store a new currency
|
|
||||||
*CurrenciesApi* | [**update_currency**](docs/CurrenciesApi.md#update_currency) | **PUT** /v1/currencies/{code} | Update existing currency.
|
|
||||||
*DataApi* | [**bulk_update_transactions**](docs/DataApi.md#bulk_update_transactions) | **POST** /v1/data/bulk/transactions | Bulk update transaction properties. For more information, see https://docs.firefly-iii.org/references/firefly-iii/api/specials/
|
|
||||||
*DataApi* | [**destroy_data**](docs/DataApi.md#destroy_data) | **DELETE** /v1/data/destroy | Endpoint to destroy user data
|
|
||||||
*DataApi* | [**export_accounts**](docs/DataApi.md#export_accounts) | **GET** /v1/data/export/accounts | Export account data from Firefly III
|
|
||||||
*DataApi* | [**export_bills**](docs/DataApi.md#export_bills) | **GET** /v1/data/export/bills | Export bills from Firefly III
|
|
||||||
*DataApi* | [**export_budgets**](docs/DataApi.md#export_budgets) | **GET** /v1/data/export/budgets | Export budgets and budget amount data from Firefly III
|
|
||||||
*DataApi* | [**export_categories**](docs/DataApi.md#export_categories) | **GET** /v1/data/export/categories | Export category data from Firefly III
|
|
||||||
*DataApi* | [**export_piggies**](docs/DataApi.md#export_piggies) | **GET** /v1/data/export/piggy-banks | Export piggy banks from Firefly III
|
|
||||||
*DataApi* | [**export_recurring**](docs/DataApi.md#export_recurring) | **GET** /v1/data/export/recurring | Export recurring transaction data from Firefly III
|
|
||||||
*DataApi* | [**export_rules**](docs/DataApi.md#export_rules) | **GET** /v1/data/export/rules | Export rule groups and rule data from Firefly III
|
|
||||||
*DataApi* | [**export_tags**](docs/DataApi.md#export_tags) | **GET** /v1/data/export/tags | Export tag data from Firefly III
|
|
||||||
*DataApi* | [**export_transactions**](docs/DataApi.md#export_transactions) | **GET** /v1/data/export/transactions | Export transaction data from Firefly III
|
|
||||||
*DataApi* | [**purge_data**](docs/DataApi.md#purge_data) | **DELETE** /v1/data/purge | Endpoint to purge user data
|
|
||||||
*InsightApi* | [**insight_expense_asset**](docs/InsightApi.md#insight_expense_asset) | **GET** /v1/insight/expense/asset | Insight into expenses, grouped by asset account.
|
|
||||||
*InsightApi* | [**insight_expense_bill**](docs/InsightApi.md#insight_expense_bill) | **GET** /v1/insight/expense/bill | Insight into expenses, grouped by bill.
|
|
||||||
*InsightApi* | [**insight_expense_budget**](docs/InsightApi.md#insight_expense_budget) | **GET** /v1/insight/expense/budget | Insight into expenses, grouped by budget.
|
|
||||||
*InsightApi* | [**insight_expense_category**](docs/InsightApi.md#insight_expense_category) | **GET** /v1/insight/expense/category | Insight into expenses, grouped by category.
|
|
||||||
*InsightApi* | [**insight_expense_expense**](docs/InsightApi.md#insight_expense_expense) | **GET** /v1/insight/expense/expense | Insight into expenses, grouped by expense account.
|
|
||||||
*InsightApi* | [**insight_expense_no_bill**](docs/InsightApi.md#insight_expense_no_bill) | **GET** /v1/insight/expense/no-bill | Insight into expenses, without bill.
|
|
||||||
*InsightApi* | [**insight_expense_no_budget**](docs/InsightApi.md#insight_expense_no_budget) | **GET** /v1/insight/expense/no-budget | Insight into expenses, without budget.
|
|
||||||
*InsightApi* | [**insight_expense_no_category**](docs/InsightApi.md#insight_expense_no_category) | **GET** /v1/insight/expense/no-category | Insight into expenses, without category.
|
|
||||||
*InsightApi* | [**insight_expense_no_tag**](docs/InsightApi.md#insight_expense_no_tag) | **GET** /v1/insight/expense/no-tag | Insight into expenses, without tag.
|
|
||||||
*InsightApi* | [**insight_expense_tag**](docs/InsightApi.md#insight_expense_tag) | **GET** /v1/insight/expense/tag | Insight into expenses, grouped by tag.
|
|
||||||
*InsightApi* | [**insight_expense_total**](docs/InsightApi.md#insight_expense_total) | **GET** /v1/insight/expense/total | Insight into total expenses.
|
|
||||||
*InsightApi* | [**insight_income_asset**](docs/InsightApi.md#insight_income_asset) | **GET** /v1/insight/income/asset | Insight into income, grouped by asset account.
|
|
||||||
*InsightApi* | [**insight_income_category**](docs/InsightApi.md#insight_income_category) | **GET** /v1/insight/income/category | Insight into income, grouped by category.
|
|
||||||
*InsightApi* | [**insight_income_no_category**](docs/InsightApi.md#insight_income_no_category) | **GET** /v1/insight/income/no-category | Insight into income, without category.
|
|
||||||
*InsightApi* | [**insight_income_no_tag**](docs/InsightApi.md#insight_income_no_tag) | **GET** /v1/insight/income/no-tag | Insight into income, without tag.
|
|
||||||
*InsightApi* | [**insight_income_revenue**](docs/InsightApi.md#insight_income_revenue) | **GET** /v1/insight/income/revenue | Insight into income, grouped by revenue account.
|
|
||||||
*InsightApi* | [**insight_income_tag**](docs/InsightApi.md#insight_income_tag) | **GET** /v1/insight/income/tag | Insight into income, grouped by tag.
|
|
||||||
*InsightApi* | [**insight_income_total**](docs/InsightApi.md#insight_income_total) | **GET** /v1/insight/income/total | Insight into total income.
|
|
||||||
*InsightApi* | [**insight_transfer_category**](docs/InsightApi.md#insight_transfer_category) | **GET** /v1/insight/transfer/category | Insight into transfers, grouped by category.
|
|
||||||
*InsightApi* | [**insight_transfer_no_category**](docs/InsightApi.md#insight_transfer_no_category) | **GET** /v1/insight/transfer/no-category | Insight into transfers, without category.
|
|
||||||
*InsightApi* | [**insight_transfer_no_tag**](docs/InsightApi.md#insight_transfer_no_tag) | **GET** /v1/insight/transfer/no-tag | Insight into expenses, without tag.
|
|
||||||
*InsightApi* | [**insight_transfer_tag**](docs/InsightApi.md#insight_transfer_tag) | **GET** /v1/insight/transfer/tag | Insight into transfers, grouped by tag.
|
|
||||||
*InsightApi* | [**insight_transfer_total**](docs/InsightApi.md#insight_transfer_total) | **GET** /v1/insight/transfer/total | Insight into total transfers.
|
|
||||||
*InsightApi* | [**insight_transfers**](docs/InsightApi.md#insight_transfers) | **GET** /v1/insight/transfer/asset | Insight into transfers, grouped by account.
|
|
||||||
*LinksApi* | [**delete_link_type**](docs/LinksApi.md#delete_link_type) | **DELETE** /v1/link-types/{id} | Permanently delete link type.
|
|
||||||
*LinksApi* | [**delete_transaction_link**](docs/LinksApi.md#delete_transaction_link) | **DELETE** /v1/transaction-links/{id} | Permanently delete link between transactions.
|
|
||||||
*LinksApi* | [**get_link_type**](docs/LinksApi.md#get_link_type) | **GET** /v1/link-types/{id} | Get single a link type.
|
|
||||||
*LinksApi* | [**get_transaction_link**](docs/LinksApi.md#get_transaction_link) | **GET** /v1/transaction-links/{id} | Get a single link.
|
|
||||||
*LinksApi* | [**list_link_type**](docs/LinksApi.md#list_link_type) | **GET** /v1/link-types | List all types of links.
|
|
||||||
*LinksApi* | [**list_transaction_by_link_type**](docs/LinksApi.md#list_transaction_by_link_type) | **GET** /v1/link-types/{id}/transactions | List all transactions under this link type.
|
|
||||||
*LinksApi* | [**list_transaction_link**](docs/LinksApi.md#list_transaction_link) | **GET** /v1/transaction-links | List all transaction links.
|
|
||||||
*LinksApi* | [**store_link_type**](docs/LinksApi.md#store_link_type) | **POST** /v1/link-types | Create a new link type
|
|
||||||
*LinksApi* | [**store_transaction_link**](docs/LinksApi.md#store_transaction_link) | **POST** /v1/transaction-links | Create a new link between transactions
|
|
||||||
*LinksApi* | [**update_link_type**](docs/LinksApi.md#update_link_type) | **PUT** /v1/link-types/{id} | Update existing link type.
|
|
||||||
*LinksApi* | [**update_transaction_link**](docs/LinksApi.md#update_transaction_link) | **PUT** /v1/transaction-links/{id} | Update an existing link between transactions.
|
|
||||||
*ObjectGroupsApi* | [**delete_object_group**](docs/ObjectGroupsApi.md#delete_object_group) | **DELETE** /v1/object-groups/{id} | Delete a object group.
|
|
||||||
*ObjectGroupsApi* | [**get_object_group**](docs/ObjectGroupsApi.md#get_object_group) | **GET** /v1/object-groups/{id} | Get a single object group.
|
|
||||||
*ObjectGroupsApi* | [**list_bill_by_object_group**](docs/ObjectGroupsApi.md#list_bill_by_object_group) | **GET** /v1/object-groups/{id}/bills | List all bills with this object group.
|
|
||||||
*ObjectGroupsApi* | [**list_object_groups**](docs/ObjectGroupsApi.md#list_object_groups) | **GET** /v1/object-groups | List all oject groups.
|
|
||||||
*ObjectGroupsApi* | [**list_piggy_bank_by_object_group**](docs/ObjectGroupsApi.md#list_piggy_bank_by_object_group) | **GET** /v1/object-groups/{id}/piggy-banks | List all piggy banks related to the object group.
|
|
||||||
*ObjectGroupsApi* | [**update_object_group**](docs/ObjectGroupsApi.md#update_object_group) | **PUT** /v1/object-groups/{id} | Update existing object group.
|
|
||||||
*PiggyBanksApi* | [**delete_piggy_bank**](docs/PiggyBanksApi.md#delete_piggy_bank) | **DELETE** /v1/piggy-banks/{id} | Delete a piggy bank.
|
|
||||||
*PiggyBanksApi* | [**get_piggy_bank**](docs/PiggyBanksApi.md#get_piggy_bank) | **GET** /v1/piggy-banks/{id} | Get a single piggy bank.
|
|
||||||
*PiggyBanksApi* | [**list_attachment_by_piggy_bank**](docs/PiggyBanksApi.md#list_attachment_by_piggy_bank) | **GET** /v1/piggy-banks/{id}/attachments | Lists all attachments.
|
|
||||||
*PiggyBanksApi* | [**list_event_by_piggy_bank**](docs/PiggyBanksApi.md#list_event_by_piggy_bank) | **GET** /v1/piggy-banks/{id}/events | List all events linked to a piggy bank.
|
|
||||||
*PiggyBanksApi* | [**list_piggy_bank**](docs/PiggyBanksApi.md#list_piggy_bank) | **GET** /v1/piggy-banks | List all piggy banks.
|
|
||||||
*PiggyBanksApi* | [**store_piggy_bank**](docs/PiggyBanksApi.md#store_piggy_bank) | **POST** /v1/piggy-banks | Store a new piggy bank
|
|
||||||
*PiggyBanksApi* | [**update_piggy_bank**](docs/PiggyBanksApi.md#update_piggy_bank) | **PUT** /v1/piggy-banks/{id} | Update existing piggy bank.
|
|
||||||
*PreferencesApi* | [**get_preference**](docs/PreferencesApi.md#get_preference) | **GET** /v1/preferences/{name} | Return a single preference.
|
|
||||||
*PreferencesApi* | [**list_preference**](docs/PreferencesApi.md#list_preference) | **GET** /v1/preferences | List all users preferences.
|
|
||||||
*PreferencesApi* | [**store_preference**](docs/PreferencesApi.md#store_preference) | **POST** /v1/preferences | Store a new preference for this user.
|
|
||||||
*PreferencesApi* | [**update_preference**](docs/PreferencesApi.md#update_preference) | **PUT** /v1/preferences/{name} | Update preference
|
|
||||||
*RecurrencesApi* | [**delete_recurrence**](docs/RecurrencesApi.md#delete_recurrence) | **DELETE** /v1/recurrences/{id} | Delete a recurring transaction.
|
|
||||||
*RecurrencesApi* | [**get_recurrence**](docs/RecurrencesApi.md#get_recurrence) | **GET** /v1/recurrences/{id} | Get a single recurring transaction.
|
|
||||||
*RecurrencesApi* | [**list_recurrence**](docs/RecurrencesApi.md#list_recurrence) | **GET** /v1/recurrences | List all recurring transactions.
|
|
||||||
*RecurrencesApi* | [**list_transaction_by_recurrence**](docs/RecurrencesApi.md#list_transaction_by_recurrence) | **GET** /v1/recurrences/{id}/transactions | List all transactions created by a recurring transaction.
|
|
||||||
*RecurrencesApi* | [**store_recurrence**](docs/RecurrencesApi.md#store_recurrence) | **POST** /v1/recurrences | Store a new recurring transaction
|
|
||||||
*RecurrencesApi* | [**update_recurrence**](docs/RecurrencesApi.md#update_recurrence) | **PUT** /v1/recurrences/{id} | Update existing recurring transaction.
|
|
||||||
*RuleGroupsApi* | [**delete_rule_group**](docs/RuleGroupsApi.md#delete_rule_group) | **DELETE** /v1/rule-groups/{id} | Delete a rule group.
|
|
||||||
*RuleGroupsApi* | [**fire_rule_group**](docs/RuleGroupsApi.md#fire_rule_group) | **POST** /v1/rule-groups/{id}/trigger | Fire the rule group on your transactions.
|
|
||||||
*RuleGroupsApi* | [**get_rule_group**](docs/RuleGroupsApi.md#get_rule_group) | **GET** /v1/rule-groups/{id} | Get a single rule group.
|
|
||||||
*RuleGroupsApi* | [**list_rule_by_group**](docs/RuleGroupsApi.md#list_rule_by_group) | **GET** /v1/rule-groups/{id}/rules | List rules in this rule group.
|
|
||||||
*RuleGroupsApi* | [**list_rule_group**](docs/RuleGroupsApi.md#list_rule_group) | **GET** /v1/rule-groups | List all rule groups.
|
|
||||||
*RuleGroupsApi* | [**store_rule_group**](docs/RuleGroupsApi.md#store_rule_group) | **POST** /v1/rule-groups | Store a new rule group.
|
|
||||||
*RuleGroupsApi* | [**test_rule_group**](docs/RuleGroupsApi.md#test_rule_group) | **GET** /v1/rule-groups/{id}/test | Test which transactions would be hit by the rule group. No changes will be made.
|
|
||||||
*RuleGroupsApi* | [**update_rule_group**](docs/RuleGroupsApi.md#update_rule_group) | **PUT** /v1/rule-groups/{id} | Update existing rule group.
|
|
||||||
*RulesApi* | [**delete_rule**](docs/RulesApi.md#delete_rule) | **DELETE** /v1/rules/{id} | Delete an rule.
|
|
||||||
*RulesApi* | [**fire_rule**](docs/RulesApi.md#fire_rule) | **POST** /v1/rules/{id}/trigger | Fire the rule on your transactions.
|
|
||||||
*RulesApi* | [**get_rule**](docs/RulesApi.md#get_rule) | **GET** /v1/rules/{id} | Get a single rule.
|
|
||||||
*RulesApi* | [**list_rule**](docs/RulesApi.md#list_rule) | **GET** /v1/rules | List all rules.
|
|
||||||
*RulesApi* | [**store_rule**](docs/RulesApi.md#store_rule) | **POST** /v1/rules | Store a new rule
|
|
||||||
*RulesApi* | [**test_rule**](docs/RulesApi.md#test_rule) | **GET** /v1/rules/{id}/test | Test which transactions would be hit by the rule. No changes will be made.
|
|
||||||
*RulesApi* | [**update_rule**](docs/RulesApi.md#update_rule) | **PUT** /v1/rules/{id} | Update existing rule.
|
|
||||||
*SearchApi* | [**search_accounts**](docs/SearchApi.md#search_accounts) | **GET** /v1/search/accounts | Search for accounts
|
|
||||||
*SearchApi* | [**search_transactions**](docs/SearchApi.md#search_transactions) | **GET** /v1/search/transactions | Search for transactions
|
|
||||||
*SummaryApi* | [**get_basic_summary**](docs/SummaryApi.md#get_basic_summary) | **GET** /v1/summary/basic | Returns basic sums of the users data.
|
|
||||||
*TagsApi* | [**delete_tag**](docs/TagsApi.md#delete_tag) | **DELETE** /v1/tags/{tag} | Delete an tag.
|
|
||||||
*TagsApi* | [**get_tag**](docs/TagsApi.md#get_tag) | **GET** /v1/tags/{tag} | Get a single tag.
|
|
||||||
*TagsApi* | [**list_attachment_by_tag**](docs/TagsApi.md#list_attachment_by_tag) | **GET** /v1/tags/{tag}/attachments | Lists all attachments.
|
|
||||||
*TagsApi* | [**list_tag**](docs/TagsApi.md#list_tag) | **GET** /v1/tags | List all tags.
|
|
||||||
*TagsApi* | [**list_transaction_by_tag**](docs/TagsApi.md#list_transaction_by_tag) | **GET** /v1/tags/{tag}/transactions | List all transactions with this tag.
|
|
||||||
*TagsApi* | [**store_tag**](docs/TagsApi.md#store_tag) | **POST** /v1/tags | Store a new tag
|
|
||||||
*TagsApi* | [**update_tag**](docs/TagsApi.md#update_tag) | **PUT** /v1/tags/{tag} | Update existing tag.
|
|
||||||
*TransactionsApi* | [**delete_transaction**](docs/TransactionsApi.md#delete_transaction) | **DELETE** /v1/transactions/{id} | Delete a transaction.
|
|
||||||
*TransactionsApi* | [**delete_transaction_journal**](docs/TransactionsApi.md#delete_transaction_journal) | **DELETE** /v1/transaction-journals/{id} | Delete split from transaction
|
|
||||||
*TransactionsApi* | [**get_transaction**](docs/TransactionsApi.md#get_transaction) | **GET** /v1/transactions/{id} | Get a single transaction.
|
|
||||||
*TransactionsApi* | [**get_transaction_by_journal**](docs/TransactionsApi.md#get_transaction_by_journal) | **GET** /v1/transaction-journals/{id} | Get a single transaction, based on one of the underlying transaction journals (transaction splits).
|
|
||||||
*TransactionsApi* | [**list_attachment_by_transaction**](docs/TransactionsApi.md#list_attachment_by_transaction) | **GET** /v1/transactions/{id}/attachments | Lists all attachments.
|
|
||||||
*TransactionsApi* | [**list_event_by_transaction**](docs/TransactionsApi.md#list_event_by_transaction) | **GET** /v1/transactions/{id}/piggy-bank-events | Lists all piggy bank events.
|
|
||||||
*TransactionsApi* | [**list_links_by_journal**](docs/TransactionsApi.md#list_links_by_journal) | **GET** /v1/transaction-journals/{id}/links | Lists all the transaction links for an individual journal (individual split).
|
|
||||||
*TransactionsApi* | [**list_transaction**](docs/TransactionsApi.md#list_transaction) | **GET** /v1/transactions | List all the user's transactions.
|
|
||||||
*TransactionsApi* | [**store_transaction**](docs/TransactionsApi.md#store_transaction) | **POST** /v1/transactions | Store a new transaction
|
|
||||||
*TransactionsApi* | [**update_transaction**](docs/TransactionsApi.md#update_transaction) | **PUT** /v1/transactions/{id} | Update existing transaction. For more information, see https://docs.firefly-iii.org/references/firefly-iii/api/specials/
|
|
||||||
*UsersApi* | [**delete_user**](docs/UsersApi.md#delete_user) | **DELETE** /v1/users/{id} | Delete a user.
|
|
||||||
*UsersApi* | [**get_user**](docs/UsersApi.md#get_user) | **GET** /v1/users/{id} | Get a single user.
|
|
||||||
*UsersApi* | [**list_user**](docs/UsersApi.md#list_user) | **GET** /v1/users | List all users.
|
|
||||||
*UsersApi* | [**store_user**](docs/UsersApi.md#store_user) | **POST** /v1/users | Store a new user
|
|
||||||
*UsersApi* | [**update_user**](docs/UsersApi.md#update_user) | **PUT** /v1/users/{id} | Update an existing user's information.
|
|
||||||
*WebhooksApi* | [**delete_webhook**](docs/WebhooksApi.md#delete_webhook) | **DELETE** /v1/webhooks/{id} | Delete a webhook.
|
|
||||||
*WebhooksApi* | [**delete_webhook_message**](docs/WebhooksApi.md#delete_webhook_message) | **DELETE** /v1/webhooks/{id}/messages/{messageId} | Delete a webhook message.
|
|
||||||
*WebhooksApi* | [**delete_webhook_message_attempt**](docs/WebhooksApi.md#delete_webhook_message_attempt) | **DELETE** /v1/webhooks/{id}/messages/{messageId}/attempts/{attemptId} | Delete a webhook attempt.
|
|
||||||
*WebhooksApi* | [**get_single_webhook_message**](docs/WebhooksApi.md#get_single_webhook_message) | **GET** /v1/webhooks/{id}/messages/{messageId} | Get a single message from a webhook.
|
|
||||||
*WebhooksApi* | [**get_single_webhook_message_attempt**](docs/WebhooksApi.md#get_single_webhook_message_attempt) | **GET** /v1/webhooks/{id}/messages/{messageId}/attempts/{attemptId} | Get a single failed attempt from a single webhook message.
|
|
||||||
*WebhooksApi* | [**get_webhook**](docs/WebhooksApi.md#get_webhook) | **GET** /v1/webhooks/{id} | Get a single webhook.
|
|
||||||
*WebhooksApi* | [**get_webhook_message_attempts**](docs/WebhooksApi.md#get_webhook_message_attempts) | **GET** /v1/webhooks/{id}/messages/{messageId}/attempts | Get all the failed attempts of a single webhook message.
|
|
||||||
*WebhooksApi* | [**get_webhook_messages**](docs/WebhooksApi.md#get_webhook_messages) | **GET** /v1/webhooks/{id}/messages | Get all the messages of a single webhook.
|
|
||||||
*WebhooksApi* | [**list_webhook**](docs/WebhooksApi.md#list_webhook) | **GET** /v1/webhooks | List all webhooks.
|
|
||||||
*WebhooksApi* | [**store_webhook**](docs/WebhooksApi.md#store_webhook) | **POST** /v1/webhooks | Store a new webhook
|
|
||||||
*WebhooksApi* | [**submit_webook**](docs/WebhooksApi.md#submit_webook) | **POST** /v1/webhooks/{id}/submit | Submit messages for a webhook.
|
|
||||||
*WebhooksApi* | [**trigger_transaction_webhook**](docs/WebhooksApi.md#trigger_transaction_webhook) | **POST** /v1/webhooks/{id}/trigger-transaction/{transactionId} | Trigger webhook for a given transaction.
|
|
||||||
*WebhooksApi* | [**update_webhook**](docs/WebhooksApi.md#update_webhook) | **PUT** /v1/webhooks/{id} | Update existing webhook.
|
|
||||||
|
|
||||||
|
|
||||||
## Documentation For Models
|
|
||||||
|
|
||||||
- [Account](docs/Account.md)
|
|
||||||
- [AccountArray](docs/AccountArray.md)
|
|
||||||
- [AccountRead](docs/AccountRead.md)
|
|
||||||
- [AccountRoleProperty](docs/AccountRoleProperty.md)
|
|
||||||
- [AccountSearchFieldFilter](docs/AccountSearchFieldFilter.md)
|
|
||||||
- [AccountSingle](docs/AccountSingle.md)
|
|
||||||
- [AccountStore](docs/AccountStore.md)
|
|
||||||
- [AccountTypeFilter](docs/AccountTypeFilter.md)
|
|
||||||
- [AccountTypeProperty](docs/AccountTypeProperty.md)
|
|
||||||
- [AccountUpdate](docs/AccountUpdate.md)
|
|
||||||
- [AttachableType](docs/AttachableType.md)
|
|
||||||
- [Attachment](docs/Attachment.md)
|
|
||||||
- [AttachmentArray](docs/AttachmentArray.md)
|
|
||||||
- [AttachmentRead](docs/AttachmentRead.md)
|
|
||||||
- [AttachmentSingle](docs/AttachmentSingle.md)
|
|
||||||
- [AttachmentStore](docs/AttachmentStore.md)
|
|
||||||
- [AttachmentUpdate](docs/AttachmentUpdate.md)
|
|
||||||
- [AutoBudgetPeriod](docs/AutoBudgetPeriod.md)
|
|
||||||
- [AutoBudgetType](docs/AutoBudgetType.md)
|
|
||||||
- [AutocompleteAccount](docs/AutocompleteAccount.md)
|
|
||||||
- [AutocompleteBill](docs/AutocompleteBill.md)
|
|
||||||
- [AutocompleteBudget](docs/AutocompleteBudget.md)
|
|
||||||
- [AutocompleteCategory](docs/AutocompleteCategory.md)
|
|
||||||
- [AutocompleteCurrency](docs/AutocompleteCurrency.md)
|
|
||||||
- [AutocompleteCurrencyCode](docs/AutocompleteCurrencyCode.md)
|
|
||||||
- [AutocompleteObjectGroup](docs/AutocompleteObjectGroup.md)
|
|
||||||
- [AutocompletePiggy](docs/AutocompletePiggy.md)
|
|
||||||
- [AutocompletePiggyBalance](docs/AutocompletePiggyBalance.md)
|
|
||||||
- [AutocompleteRecurrence](docs/AutocompleteRecurrence.md)
|
|
||||||
- [AutocompleteRule](docs/AutocompleteRule.md)
|
|
||||||
- [AutocompleteRuleGroup](docs/AutocompleteRuleGroup.md)
|
|
||||||
- [AutocompleteTag](docs/AutocompleteTag.md)
|
|
||||||
- [AutocompleteTransaction](docs/AutocompleteTransaction.md)
|
|
||||||
- [AutocompleteTransactionId](docs/AutocompleteTransactionId.md)
|
|
||||||
- [AutocompleteTransactionType](docs/AutocompleteTransactionType.md)
|
|
||||||
- [AvailableBudget](docs/AvailableBudget.md)
|
|
||||||
- [AvailableBudgetArray](docs/AvailableBudgetArray.md)
|
|
||||||
- [AvailableBudgetRead](docs/AvailableBudgetRead.md)
|
|
||||||
- [AvailableBudgetSingle](docs/AvailableBudgetSingle.md)
|
|
||||||
- [BadRequestResponse](docs/BadRequestResponse.md)
|
|
||||||
- [BasicSummaryEntry](docs/BasicSummaryEntry.md)
|
|
||||||
- [Bill](docs/Bill.md)
|
|
||||||
- [BillArray](docs/BillArray.md)
|
|
||||||
- [BillPaidDatesInner](docs/BillPaidDatesInner.md)
|
|
||||||
- [BillRead](docs/BillRead.md)
|
|
||||||
- [BillRepeatFrequency](docs/BillRepeatFrequency.md)
|
|
||||||
- [BillSingle](docs/BillSingle.md)
|
|
||||||
- [BillStore](docs/BillStore.md)
|
|
||||||
- [BillUpdate](docs/BillUpdate.md)
|
|
||||||
- [Budget](docs/Budget.md)
|
|
||||||
- [BudgetArray](docs/BudgetArray.md)
|
|
||||||
- [BudgetLimit](docs/BudgetLimit.md)
|
|
||||||
- [BudgetLimitArray](docs/BudgetLimitArray.md)
|
|
||||||
- [BudgetLimitRead](docs/BudgetLimitRead.md)
|
|
||||||
- [BudgetLimitSingle](docs/BudgetLimitSingle.md)
|
|
||||||
- [BudgetLimitStore](docs/BudgetLimitStore.md)
|
|
||||||
- [BudgetRead](docs/BudgetRead.md)
|
|
||||||
- [BudgetSingle](docs/BudgetSingle.md)
|
|
||||||
- [BudgetSpent](docs/BudgetSpent.md)
|
|
||||||
- [BudgetStore](docs/BudgetStore.md)
|
|
||||||
- [BudgetUpdate](docs/BudgetUpdate.md)
|
|
||||||
- [Category](docs/Category.md)
|
|
||||||
- [CategoryArray](docs/CategoryArray.md)
|
|
||||||
- [CategoryEarned](docs/CategoryEarned.md)
|
|
||||||
- [CategoryRead](docs/CategoryRead.md)
|
|
||||||
- [CategorySingle](docs/CategorySingle.md)
|
|
||||||
- [CategorySpent](docs/CategorySpent.md)
|
|
||||||
- [CategoryUpdate](docs/CategoryUpdate.md)
|
|
||||||
- [ChartDataPoint](docs/ChartDataPoint.md)
|
|
||||||
- [ChartDataSet](docs/ChartDataSet.md)
|
|
||||||
- [ConfigValueFilter](docs/ConfigValueFilter.md)
|
|
||||||
- [ConfigValueUpdateFilter](docs/ConfigValueUpdateFilter.md)
|
|
||||||
- [Configuration](docs/Configuration.md)
|
|
||||||
- [ConfigurationSingle](docs/ConfigurationSingle.md)
|
|
||||||
- [ConfigurationUpdate](docs/ConfigurationUpdate.md)
|
|
||||||
- [CreditCardTypeProperty](docs/CreditCardTypeProperty.md)
|
|
||||||
- [CronResult](docs/CronResult.md)
|
|
||||||
- [CronResultRow](docs/CronResultRow.md)
|
|
||||||
- [Currency](docs/Currency.md)
|
|
||||||
- [CurrencyArray](docs/CurrencyArray.md)
|
|
||||||
- [CurrencyRead](docs/CurrencyRead.md)
|
|
||||||
- [CurrencySingle](docs/CurrencySingle.md)
|
|
||||||
- [CurrencyStore](docs/CurrencyStore.md)
|
|
||||||
- [CurrencyUpdate](docs/CurrencyUpdate.md)
|
|
||||||
- [DataDestroyObject](docs/DataDestroyObject.md)
|
|
||||||
- [ExportFileFilter](docs/ExportFileFilter.md)
|
|
||||||
- [InsightGroupEntry](docs/InsightGroupEntry.md)
|
|
||||||
- [InsightTotalEntry](docs/InsightTotalEntry.md)
|
|
||||||
- [InsightTransferEntry](docs/InsightTransferEntry.md)
|
|
||||||
- [InterestPeriodProperty](docs/InterestPeriodProperty.md)
|
|
||||||
- [InternalExceptionResponse](docs/InternalExceptionResponse.md)
|
|
||||||
- [LiabilityDirectionProperty](docs/LiabilityDirectionProperty.md)
|
|
||||||
- [LiabilityTypeProperty](docs/LiabilityTypeProperty.md)
|
|
||||||
- [LinkType](docs/LinkType.md)
|
|
||||||
- [LinkTypeArray](docs/LinkTypeArray.md)
|
|
||||||
- [LinkTypeRead](docs/LinkTypeRead.md)
|
|
||||||
- [LinkTypeSingle](docs/LinkTypeSingle.md)
|
|
||||||
- [LinkTypeUpdate](docs/LinkTypeUpdate.md)
|
|
||||||
- [Meta](docs/Meta.md)
|
|
||||||
- [MetaPagination](docs/MetaPagination.md)
|
|
||||||
- [NotFoundResponse](docs/NotFoundResponse.md)
|
|
||||||
- [ObjectGroup](docs/ObjectGroup.md)
|
|
||||||
- [ObjectGroupArray](docs/ObjectGroupArray.md)
|
|
||||||
- [ObjectGroupRead](docs/ObjectGroupRead.md)
|
|
||||||
- [ObjectGroupSingle](docs/ObjectGroupSingle.md)
|
|
||||||
- [ObjectGroupUpdate](docs/ObjectGroupUpdate.md)
|
|
||||||
- [ObjectLink](docs/ObjectLink.md)
|
|
||||||
- [ObjectLink0](docs/ObjectLink0.md)
|
|
||||||
- [PageLink](docs/PageLink.md)
|
|
||||||
- [PiggyBank](docs/PiggyBank.md)
|
|
||||||
- [PiggyBankArray](docs/PiggyBankArray.md)
|
|
||||||
- [PiggyBankEvent](docs/PiggyBankEvent.md)
|
|
||||||
- [PiggyBankEventArray](docs/PiggyBankEventArray.md)
|
|
||||||
- [PiggyBankEventRead](docs/PiggyBankEventRead.md)
|
|
||||||
- [PiggyBankRead](docs/PiggyBankRead.md)
|
|
||||||
- [PiggyBankSingle](docs/PiggyBankSingle.md)
|
|
||||||
- [PiggyBankStore](docs/PiggyBankStore.md)
|
|
||||||
- [PiggyBankUpdate](docs/PiggyBankUpdate.md)
|
|
||||||
- [PolymorphicProperty](docs/PolymorphicProperty.md)
|
|
||||||
- [Preference](docs/Preference.md)
|
|
||||||
- [PreferenceArray](docs/PreferenceArray.md)
|
|
||||||
- [PreferenceRead](docs/PreferenceRead.md)
|
|
||||||
- [PreferenceSingle](docs/PreferenceSingle.md)
|
|
||||||
- [PreferenceUpdate](docs/PreferenceUpdate.md)
|
|
||||||
- [Recurrence](docs/Recurrence.md)
|
|
||||||
- [RecurrenceArray](docs/RecurrenceArray.md)
|
|
||||||
- [RecurrenceRead](docs/RecurrenceRead.md)
|
|
||||||
- [RecurrenceRepetition](docs/RecurrenceRepetition.md)
|
|
||||||
- [RecurrenceRepetitionStore](docs/RecurrenceRepetitionStore.md)
|
|
||||||
- [RecurrenceRepetitionType](docs/RecurrenceRepetitionType.md)
|
|
||||||
- [RecurrenceRepetitionUpdate](docs/RecurrenceRepetitionUpdate.md)
|
|
||||||
- [RecurrenceSingle](docs/RecurrenceSingle.md)
|
|
||||||
- [RecurrenceStore](docs/RecurrenceStore.md)
|
|
||||||
- [RecurrenceTransaction](docs/RecurrenceTransaction.md)
|
|
||||||
- [RecurrenceTransactionStore](docs/RecurrenceTransactionStore.md)
|
|
||||||
- [RecurrenceTransactionType](docs/RecurrenceTransactionType.md)
|
|
||||||
- [RecurrenceTransactionUpdate](docs/RecurrenceTransactionUpdate.md)
|
|
||||||
- [RecurrenceUpdate](docs/RecurrenceUpdate.md)
|
|
||||||
- [Rule](docs/Rule.md)
|
|
||||||
- [RuleAction](docs/RuleAction.md)
|
|
||||||
- [RuleActionKeyword](docs/RuleActionKeyword.md)
|
|
||||||
- [RuleActionStore](docs/RuleActionStore.md)
|
|
||||||
- [RuleActionUpdate](docs/RuleActionUpdate.md)
|
|
||||||
- [RuleArray](docs/RuleArray.md)
|
|
||||||
- [RuleGroup](docs/RuleGroup.md)
|
|
||||||
- [RuleGroupArray](docs/RuleGroupArray.md)
|
|
||||||
- [RuleGroupRead](docs/RuleGroupRead.md)
|
|
||||||
- [RuleGroupSingle](docs/RuleGroupSingle.md)
|
|
||||||
- [RuleGroupStore](docs/RuleGroupStore.md)
|
|
||||||
- [RuleGroupUpdate](docs/RuleGroupUpdate.md)
|
|
||||||
- [RuleRead](docs/RuleRead.md)
|
|
||||||
- [RuleSingle](docs/RuleSingle.md)
|
|
||||||
- [RuleStore](docs/RuleStore.md)
|
|
||||||
- [RuleTrigger](docs/RuleTrigger.md)
|
|
||||||
- [RuleTriggerKeyword](docs/RuleTriggerKeyword.md)
|
|
||||||
- [RuleTriggerStore](docs/RuleTriggerStore.md)
|
|
||||||
- [RuleTriggerType](docs/RuleTriggerType.md)
|
|
||||||
- [RuleTriggerUpdate](docs/RuleTriggerUpdate.md)
|
|
||||||
- [RuleUpdate](docs/RuleUpdate.md)
|
|
||||||
- [ShortAccountTypeProperty](docs/ShortAccountTypeProperty.md)
|
|
||||||
- [SystemInfo](docs/SystemInfo.md)
|
|
||||||
- [SystemInfoData](docs/SystemInfoData.md)
|
|
||||||
- [TagArray](docs/TagArray.md)
|
|
||||||
- [TagModel](docs/TagModel.md)
|
|
||||||
- [TagModelStore](docs/TagModelStore.md)
|
|
||||||
- [TagModelUpdate](docs/TagModelUpdate.md)
|
|
||||||
- [TagRead](docs/TagRead.md)
|
|
||||||
- [TagSingle](docs/TagSingle.md)
|
|
||||||
- [Transaction](docs/Transaction.md)
|
|
||||||
- [TransactionArray](docs/TransactionArray.md)
|
|
||||||
- [TransactionLink](docs/TransactionLink.md)
|
|
||||||
- [TransactionLinkArray](docs/TransactionLinkArray.md)
|
|
||||||
- [TransactionLinkRead](docs/TransactionLinkRead.md)
|
|
||||||
- [TransactionLinkSingle](docs/TransactionLinkSingle.md)
|
|
||||||
- [TransactionLinkStore](docs/TransactionLinkStore.md)
|
|
||||||
- [TransactionLinkUpdate](docs/TransactionLinkUpdate.md)
|
|
||||||
- [TransactionRead](docs/TransactionRead.md)
|
|
||||||
- [TransactionSingle](docs/TransactionSingle.md)
|
|
||||||
- [TransactionSplit](docs/TransactionSplit.md)
|
|
||||||
- [TransactionSplitStore](docs/TransactionSplitStore.md)
|
|
||||||
- [TransactionSplitUpdate](docs/TransactionSplitUpdate.md)
|
|
||||||
- [TransactionStore](docs/TransactionStore.md)
|
|
||||||
- [TransactionTypeFilter](docs/TransactionTypeFilter.md)
|
|
||||||
- [TransactionTypeProperty](docs/TransactionTypeProperty.md)
|
|
||||||
- [TransactionUpdate](docs/TransactionUpdate.md)
|
|
||||||
- [UnauthenticatedResponse](docs/UnauthenticatedResponse.md)
|
|
||||||
- [User](docs/User.md)
|
|
||||||
- [UserArray](docs/UserArray.md)
|
|
||||||
- [UserBlockedCodeProperty](docs/UserBlockedCodeProperty.md)
|
|
||||||
- [UserRead](docs/UserRead.md)
|
|
||||||
- [UserRoleProperty](docs/UserRoleProperty.md)
|
|
||||||
- [UserSingle](docs/UserSingle.md)
|
|
||||||
- [ValidationErrorResponse](docs/ValidationErrorResponse.md)
|
|
||||||
- [ValidationErrorResponseErrors](docs/ValidationErrorResponseErrors.md)
|
|
||||||
- [Webhook](docs/Webhook.md)
|
|
||||||
- [WebhookArray](docs/WebhookArray.md)
|
|
||||||
- [WebhookAttempt](docs/WebhookAttempt.md)
|
|
||||||
- [WebhookAttemptArray](docs/WebhookAttemptArray.md)
|
|
||||||
- [WebhookAttemptRead](docs/WebhookAttemptRead.md)
|
|
||||||
- [WebhookAttemptSingle](docs/WebhookAttemptSingle.md)
|
|
||||||
- [WebhookDelivery](docs/WebhookDelivery.md)
|
|
||||||
- [WebhookMessage](docs/WebhookMessage.md)
|
|
||||||
- [WebhookMessageArray](docs/WebhookMessageArray.md)
|
|
||||||
- [WebhookMessageRead](docs/WebhookMessageRead.md)
|
|
||||||
- [WebhookMessageSingle](docs/WebhookMessageSingle.md)
|
|
||||||
- [WebhookRead](docs/WebhookRead.md)
|
|
||||||
- [WebhookResponse](docs/WebhookResponse.md)
|
|
||||||
- [WebhookSingle](docs/WebhookSingle.md)
|
|
||||||
- [WebhookStore](docs/WebhookStore.md)
|
|
||||||
- [WebhookTrigger](docs/WebhookTrigger.md)
|
|
||||||
- [WebhookUpdate](docs/WebhookUpdate.md)
|
|
||||||
|
|
||||||
|
|
||||||
To get access to the crate's generated documentation, use:
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo doc --open
|
|
||||||
```
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
james@firefly-iii.org
|
|
||||||
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
# \AboutApi
|
|
||||||
|
|
||||||
All URIs are relative to *https://demo.firefly-iii.org/api*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**get_about**](AboutApi.md#get_about) | **GET** /v1/about | System information end point.
|
|
||||||
[**get_cron**](AboutApi.md#get_cron) | **GET** /v1/cron/{cliToken} | Cron job endpoint
|
|
||||||
[**get_current_user**](AboutApi.md#get_current_user) | **GET** /v1/about/user | Currently authenticated user endpoint.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## get_about
|
|
||||||
|
|
||||||
> models::SystemInfo get_about(x_trace_id)
|
|
||||||
System information end point.
|
|
||||||
|
|
||||||
Returns general system information and versions of the (supporting) software.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::SystemInfo**](SystemInfo.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_cron
|
|
||||||
|
|
||||||
> models::CronResult get_cron(cli_token, x_trace_id, date, force)
|
|
||||||
Cron job endpoint
|
|
||||||
|
|
||||||
Firefly III has one endpoint for its various cron related tasks. Send a GET to this endpoint to run the cron. The cron requires the CLI token to be present. The cron job will fire for all users.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**cli_token** | **String** | The CLI token of any user in Firefly III, required to run the cron job. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**date** | Option<**String**> | A date formatted YYYY-MM-DD. This can be used to make the cron job pretend it's running on another day. | |
|
|
||||||
**force** | Option<**bool**> | Forces the cron job to fire, regardless of whether it has fired before. This may result in double transactions or weird budgets, so be careful. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::CronResult**](CronResult.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_current_user
|
|
||||||
|
|
||||||
> models::UserSingle get_current_user(x_trace_id)
|
|
||||||
Currently authenticated user endpoint.
|
|
||||||
|
|
||||||
Returns the currently authenticated user.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::UserSingle**](UserSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Account
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**created_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**updated_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**active** | Option<**bool**> | If omitted, defaults to true. | [optional][default to true]
|
|
||||||
**order** | Option<**i32**> | Order of the account. Is NULL if account is not asset or liability. | [optional]
|
|
||||||
**name** | **String** | |
|
|
||||||
**r#type** | [**models::ShortAccountTypeProperty**](ShortAccountTypeProperty.md) | |
|
|
||||||
**account_role** | Option<[**models::AccountRoleProperty**](AccountRoleProperty.md)> | | [optional]
|
|
||||||
**currency_id** | Option<**String**> | Use either currency_id or currency_code. Defaults to the user's default currency. | [optional]
|
|
||||||
**currency_code** | Option<**String**> | Use either currency_id or currency_code. Defaults to the user's default currency. | [optional]
|
|
||||||
**currency_symbol** | Option<**String**> | | [optional][readonly]
|
|
||||||
**currency_decimal_places** | Option<**i32**> | | [optional][readonly]
|
|
||||||
**current_balance** | Option<**String**> | | [optional][readonly]
|
|
||||||
**current_balance_date** | Option<**String**> | The timestamp for this date is always 23:59:59, to indicate it's the balance at the very END of that particular day. | [optional][readonly]
|
|
||||||
**iban** | Option<**String**> | | [optional]
|
|
||||||
**bic** | Option<**String**> | | [optional]
|
|
||||||
**account_number** | Option<**String**> | | [optional]
|
|
||||||
**opening_balance** | Option<**String**> | Represents the opening balance, the initial amount this account holds. | [optional]
|
|
||||||
**current_debt** | Option<**String**> | Represents the current debt for liabilities. | [optional]
|
|
||||||
**opening_balance_date** | Option<**String**> | Represents the date of the opening balance. | [optional]
|
|
||||||
**virtual_balance** | Option<**String**> | | [optional]
|
|
||||||
**include_net_worth** | Option<**bool**> | If omitted, defaults to true. | [optional][default to true]
|
|
||||||
**credit_card_type** | Option<[**models::CreditCardTypeProperty**](CreditCardTypeProperty.md)> | | [optional]
|
|
||||||
**monthly_payment_date** | Option<**String**> | Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank. | [optional]
|
|
||||||
**liability_type** | Option<[**models::LiabilityTypeProperty**](LiabilityTypeProperty.md)> | | [optional]
|
|
||||||
**liability_direction** | Option<[**models::LiabilityDirectionProperty**](LiabilityDirectionProperty.md)> | | [optional]
|
|
||||||
**interest** | Option<**String**> | Mandatory when type is liability. Interest percentage. | [optional]
|
|
||||||
**interest_period** | Option<[**models::InterestPeriodProperty**](InterestPeriodProperty.md)> | | [optional]
|
|
||||||
**notes** | Option<**String**> | | [optional]
|
|
||||||
**latitude** | Option<**f64**> | Latitude of the accounts's location, if applicable. Can be used to draw a map. | [optional]
|
|
||||||
**longitude** | Option<**f64**> | Latitude of the accounts's location, if applicable. Can be used to draw a map. | [optional]
|
|
||||||
**zoom_level** | Option<**i32**> | Zoom level for the map, if drawn. This to set the box right. Unfortunately this is a proprietary value because each map provider has different zoom levels. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# AccountArray
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**Vec<models::AccountRead>**](AccountRead.md) | |
|
|
||||||
**meta** | [**models::Meta**](Meta.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AccountRead
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**r#type** | **String** | Immutable value |
|
|
||||||
**id** | **String** | |
|
|
||||||
**attributes** | [**models::Account**](Account.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# AccountRoleProperty
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| DefaultAsset | defaultAsset |
|
|
||||||
| SharedAsset | sharedAsset |
|
|
||||||
| SavingAsset | savingAsset |
|
|
||||||
| CcAsset | ccAsset |
|
|
||||||
| CashWalletAsset | cashWalletAsset |
|
|
||||||
| Null | null |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# AccountSearchFieldFilter
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| All | all |
|
|
||||||
| Iban | iban |
|
|
||||||
| Name | name |
|
|
||||||
| Number | number |
|
|
||||||
| Id | id |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# AccountSingle
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**models::AccountRead**](AccountRead.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# AccountStore
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**name** | **String** | |
|
|
||||||
**r#type** | [**models::ShortAccountTypeProperty**](ShortAccountTypeProperty.md) | |
|
|
||||||
**iban** | Option<**String**> | | [optional]
|
|
||||||
**bic** | Option<**String**> | | [optional]
|
|
||||||
**account_number** | Option<**String**> | | [optional]
|
|
||||||
**opening_balance** | Option<**String**> | Represents the opening balance, the initial amount this account holds. | [optional]
|
|
||||||
**opening_balance_date** | Option<**String**> | Represents the date of the opening balance. | [optional]
|
|
||||||
**virtual_balance** | Option<**String**> | | [optional]
|
|
||||||
**currency_id** | Option<**String**> | Use either currency_id or currency_code. Defaults to the user's default currency. | [optional]
|
|
||||||
**currency_code** | Option<**String**> | Use either currency_id or currency_code. Defaults to the user's default currency. | [optional]
|
|
||||||
**active** | Option<**bool**> | If omitted, defaults to true. | [optional][default to true]
|
|
||||||
**order** | Option<**i32**> | Order of the account | [optional]
|
|
||||||
**include_net_worth** | Option<**bool**> | If omitted, defaults to true. | [optional][default to true]
|
|
||||||
**account_role** | Option<[**models::AccountRoleProperty**](AccountRoleProperty.md)> | | [optional]
|
|
||||||
**credit_card_type** | Option<[**models::CreditCardTypeProperty**](CreditCardTypeProperty.md)> | | [optional]
|
|
||||||
**monthly_payment_date** | Option<**String**> | Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank. | [optional]
|
|
||||||
**liability_type** | Option<[**models::LiabilityTypeProperty**](LiabilityTypeProperty.md)> | | [optional]
|
|
||||||
**liability_direction** | Option<[**models::LiabilityDirectionProperty**](LiabilityDirectionProperty.md)> | | [optional]
|
|
||||||
**interest** | Option<**String**> | Mandatory when type is liability. Interest percentage. | [optional][default to 0]
|
|
||||||
**interest_period** | Option<[**models::InterestPeriodProperty**](InterestPeriodProperty.md)> | | [optional]
|
|
||||||
**notes** | Option<**String**> | | [optional]
|
|
||||||
**latitude** | Option<**f64**> | Latitude of the accounts's location, if applicable. Can be used to draw a map. | [optional]
|
|
||||||
**longitude** | Option<**f64**> | Latitude of the accounts's location, if applicable. Can be used to draw a map. | [optional]
|
|
||||||
**zoom_level** | Option<**i32**> | Zoom level for the map, if drawn. This to set the box right. Unfortunately this is a proprietary value because each map provider has different zoom levels. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# AccountTypeFilter
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| All | all |
|
|
||||||
| Asset | asset |
|
|
||||||
| Cash | cash |
|
|
||||||
| Expense | expense |
|
|
||||||
| Revenue | revenue |
|
|
||||||
| Special | special |
|
|
||||||
| Hidden | hidden |
|
|
||||||
| Liability | liability |
|
|
||||||
| Liabilities | liabilities |
|
|
||||||
| DefaultAccount | Default account |
|
|
||||||
| CashAccount | Cash account |
|
|
||||||
| AssetAccount | Asset account |
|
|
||||||
| ExpenseAccount | Expense account |
|
|
||||||
| RevenueAccount | Revenue account |
|
|
||||||
| InitialBalanceAccount | Initial balance account |
|
|
||||||
| BeneficiaryAccount | Beneficiary account |
|
|
||||||
| ImportAccount | Import account |
|
|
||||||
| ReconciliationAccount | Reconciliation account |
|
|
||||||
| Loan | Loan |
|
|
||||||
| Debt | Debt |
|
|
||||||
| Mortgage | Mortgage |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# AccountTypeProperty
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| DefaultAccount | Default account |
|
|
||||||
| CashAccount | Cash account |
|
|
||||||
| AssetAccount | Asset account |
|
|
||||||
| ExpenseAccount | Expense account |
|
|
||||||
| RevenueAccount | Revenue account |
|
|
||||||
| InitialBalanceAccount | Initial balance account |
|
|
||||||
| BeneficiaryAccount | Beneficiary account |
|
|
||||||
| ImportAccount | Import account |
|
|
||||||
| ReconciliationAccount | Reconciliation account |
|
|
||||||
| Loan | Loan |
|
|
||||||
| Debt | Debt |
|
|
||||||
| Mortgage | Mortgage |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# AccountUpdate
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**name** | **String** | |
|
|
||||||
**iban** | Option<**String**> | | [optional]
|
|
||||||
**bic** | Option<**String**> | | [optional]
|
|
||||||
**account_number** | Option<**String**> | | [optional]
|
|
||||||
**opening_balance** | Option<**String**> | | [optional]
|
|
||||||
**opening_balance_date** | Option<**String**> | | [optional]
|
|
||||||
**virtual_balance** | Option<**String**> | | [optional]
|
|
||||||
**currency_id** | Option<**String**> | Use either currency_id or currency_code. Defaults to the user's default currency. | [optional]
|
|
||||||
**currency_code** | Option<**String**> | Use either currency_id or currency_code. Defaults to the user's default currency. | [optional]
|
|
||||||
**active** | Option<**bool**> | If omitted, defaults to true. | [optional][default to true]
|
|
||||||
**order** | Option<**i32**> | Order of the account | [optional]
|
|
||||||
**include_net_worth** | Option<**bool**> | If omitted, defaults to true. | [optional][default to true]
|
|
||||||
**account_role** | Option<[**models::AccountRoleProperty**](AccountRoleProperty.md)> | | [optional]
|
|
||||||
**credit_card_type** | Option<[**models::CreditCardTypeProperty**](CreditCardTypeProperty.md)> | | [optional]
|
|
||||||
**monthly_payment_date** | Option<**String**> | Mandatory when the account_role is ccAsset. Moment at which CC payment installments are asked for by the bank. | [optional]
|
|
||||||
**liability_type** | Option<[**models::LiabilityTypeProperty**](LiabilityTypeProperty.md)> | | [optional]
|
|
||||||
**interest** | Option<**String**> | Mandatory when type is liability. Interest percentage. | [optional]
|
|
||||||
**interest_period** | Option<[**models::InterestPeriodProperty**](InterestPeriodProperty.md)> | | [optional]
|
|
||||||
**notes** | Option<**String**> | | [optional]
|
|
||||||
**latitude** | Option<**f64**> | Latitude of the account's location, if applicable. Can be used to draw a map. If omitted, the existing location will be kept. If submitted as NULL, the current location will be removed. | [optional]
|
|
||||||
**longitude** | Option<**f64**> | Latitude of the account's location, if applicable. Can be used to draw a map. If omitted, the existing location will be kept. If submitted as NULL, the current location will be removed. | [optional]
|
|
||||||
**zoom_level** | Option<**i32**> | Zoom level for the map, if drawn. This to set the box right. Unfortunately this is a proprietary value because each map provider has different zoom levels. If omitted, the existing location will be kept. If submitted as NULL, the current location will be removed. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
# \AccountsApi
|
|
||||||
|
|
||||||
All URIs are relative to *https://demo.firefly-iii.org/api*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**delete_account**](AccountsApi.md#delete_account) | **DELETE** /v1/accounts/{id} | Permanently delete account.
|
|
||||||
[**get_account**](AccountsApi.md#get_account) | **GET** /v1/accounts/{id} | Get single account.
|
|
||||||
[**list_account**](AccountsApi.md#list_account) | **GET** /v1/accounts | List all accounts.
|
|
||||||
[**list_attachment_by_account**](AccountsApi.md#list_attachment_by_account) | **GET** /v1/accounts/{id}/attachments | Lists all attachments.
|
|
||||||
[**list_piggy_bank_by_account**](AccountsApi.md#list_piggy_bank_by_account) | **GET** /v1/accounts/{id}/piggy-banks | List all piggy banks related to the account.
|
|
||||||
[**list_transaction_by_account**](AccountsApi.md#list_transaction_by_account) | **GET** /v1/accounts/{id}/transactions | List all transactions related to the account.
|
|
||||||
[**store_account**](AccountsApi.md#store_account) | **POST** /v1/accounts | Create new account.
|
|
||||||
[**update_account**](AccountsApi.md#update_account) | **PUT** /v1/accounts/{id} | Update existing account.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## delete_account
|
|
||||||
|
|
||||||
> delete_account(id, x_trace_id)
|
|
||||||
Permanently delete account.
|
|
||||||
|
|
||||||
Will permanently delete an account. Any associated transactions and piggy banks are ALSO deleted. Cannot be recovered from.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the account. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
(empty response body)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_account
|
|
||||||
|
|
||||||
> models::AccountSingle get_account(id, x_trace_id, date)
|
|
||||||
Get single account.
|
|
||||||
|
|
||||||
Returns a single account by its ID.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the account. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**date** | Option<**String**> | A date formatted YYYY-MM-DD. When added to the request, Firefly III will show the account's balance on that day. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AccountSingle**](AccountSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_account
|
|
||||||
|
|
||||||
> models::AccountArray list_account(x_trace_id, limit, page, date, r#type)
|
|
||||||
List all accounts.
|
|
||||||
|
|
||||||
This endpoint returns a list of all the accounts owned by the authenticated user.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**limit** | Option<**i32**> | Number of items per page. The default pagination is per 50 items. | |
|
|
||||||
**page** | Option<**i32**> | Page number. The default pagination is per 50 items. | |
|
|
||||||
**date** | Option<**String**> | A date formatted YYYY-MM-DD. When added to the request, Firefly III will show the account's balance on that day. | |
|
|
||||||
**r#type** | Option<[**AccountTypeFilter**](.md)> | Optional filter on the account type(s) returned | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AccountArray**](AccountArray.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_attachment_by_account
|
|
||||||
|
|
||||||
> models::AttachmentArray list_attachment_by_account(id, x_trace_id, limit, page)
|
|
||||||
Lists all attachments.
|
|
||||||
|
|
||||||
Lists all attachments.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the account. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**limit** | Option<**i32**> | Number of items per page. The default pagination is per 50 items. | |
|
|
||||||
**page** | Option<**i32**> | Page number. The default pagination is per 50 items. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AttachmentArray**](AttachmentArray.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_piggy_bank_by_account
|
|
||||||
|
|
||||||
> models::PiggyBankArray list_piggy_bank_by_account(id, x_trace_id, limit, page)
|
|
||||||
List all piggy banks related to the account.
|
|
||||||
|
|
||||||
This endpoint returns a list of all the piggy banks connected to the account.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the account. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**limit** | Option<**i32**> | Number of items per page. The default pagination is per 50 items. | |
|
|
||||||
**page** | Option<**i32**> | Page number. The default pagination is per 50 items. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::PiggyBankArray**](PiggyBankArray.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_transaction_by_account
|
|
||||||
|
|
||||||
> models::TransactionArray list_transaction_by_account(id, x_trace_id, limit, page, start, end, r#type)
|
|
||||||
List all transactions related to the account.
|
|
||||||
|
|
||||||
This endpoint returns a list of all the transactions connected to the account.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the account. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**limit** | Option<**i32**> | Number of items per page. The default pagination is per 50 items. | |
|
|
||||||
**page** | Option<**i32**> | Page number. The default pagination is per 50 items. | |
|
|
||||||
**start** | Option<**String**> | A date formatted YYYY-MM-DD. | |
|
|
||||||
**end** | Option<**String**> | A date formatted YYYY-MM-DD. | |
|
|
||||||
**r#type** | Option<[**TransactionTypeFilter**](.md)> | Optional filter on the transaction type(s) returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::TransactionArray**](TransactionArray.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## store_account
|
|
||||||
|
|
||||||
> models::AccountSingle store_account(account_store, x_trace_id)
|
|
||||||
Create new account.
|
|
||||||
|
|
||||||
Creates a new account. The data required can be submitted as a JSON body or as a list of parameters (in key=value pairs, like a webform).
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**account_store** | [**AccountStore**](AccountStore.md) | JSON array with the necessary account information or key=value pairs. See the model for the exact specifications. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AccountSingle**](AccountSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json, application/x-www-form-urlencoded
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## update_account
|
|
||||||
|
|
||||||
> models::AccountSingle update_account(id, account_update, x_trace_id)
|
|
||||||
Update existing account.
|
|
||||||
|
|
||||||
Used to update a single account. All fields that are not submitted will be cleared (set to NULL). The model will tell you which fields are mandatory.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the account. | [required] |
|
|
||||||
**account_update** | [**AccountUpdate**](AccountUpdate.md) | JSON array or formdata with updated account information. See the model for the exact specifications. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AccountSingle**](AccountSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json, application/x-www-form-urlencoded
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# AttachableType
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| Account | Account |
|
|
||||||
| Budget | Budget |
|
|
||||||
| Bill | Bill |
|
|
||||||
| TransactionJournal | TransactionJournal |
|
|
||||||
| PiggyBank | PiggyBank |
|
|
||||||
| Tag | Tag |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Attachment
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**created_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**updated_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**attachable_type** | [**models::AttachableType**](AttachableType.md) | |
|
|
||||||
**attachable_id** | **String** | ID of the model this attachment is linked to. |
|
|
||||||
**md5** | Option<**String**> | MD5 hash of the file for basic duplicate detection. | [optional]
|
|
||||||
**filename** | **String** | |
|
|
||||||
**download_url** | Option<**String**> | | [optional]
|
|
||||||
**upload_url** | Option<**String**> | | [optional]
|
|
||||||
**title** | Option<**String**> | | [optional]
|
|
||||||
**notes** | Option<**String**> | | [optional]
|
|
||||||
**mime** | Option<**String**> | | [optional][readonly]
|
|
||||||
**size** | Option<**i32**> | | [optional][readonly]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# AttachmentArray
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**Vec<models::AttachmentRead>**](AttachmentRead.md) | |
|
|
||||||
**meta** | [**models::Meta**](Meta.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# AttachmentRead
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**r#type** | **String** | Immutable value |
|
|
||||||
**id** | **String** | |
|
|
||||||
**attributes** | [**models::Attachment**](Attachment.md) | |
|
|
||||||
**links** | [**models::ObjectLink**](ObjectLink.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# AttachmentSingle
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**models::AttachmentRead**](AttachmentRead.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# AttachmentStore
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**filename** | **String** | |
|
|
||||||
**attachable_type** | [**models::AttachableType**](AttachableType.md) | |
|
|
||||||
**attachable_id** | **String** | ID of the model this attachment is linked to. |
|
|
||||||
**title** | Option<**String**> | | [optional]
|
|
||||||
**notes** | Option<**String**> | | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AttachmentUpdate
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**filename** | Option<**String**> | | [optional]
|
|
||||||
**title** | Option<**String**> | | [optional]
|
|
||||||
**notes** | Option<**String**> | | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
# \AttachmentsApi
|
|
||||||
|
|
||||||
All URIs are relative to *https://demo.firefly-iii.org/api*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**delete_attachment**](AttachmentsApi.md#delete_attachment) | **DELETE** /v1/attachments/{id} | Delete an attachment.
|
|
||||||
[**download_attachment**](AttachmentsApi.md#download_attachment) | **GET** /v1/attachments/{id}/download | Download a single attachment.
|
|
||||||
[**get_attachment**](AttachmentsApi.md#get_attachment) | **GET** /v1/attachments/{id} | Get a single attachment.
|
|
||||||
[**list_attachment**](AttachmentsApi.md#list_attachment) | **GET** /v1/attachments | List all attachments.
|
|
||||||
[**store_attachment**](AttachmentsApi.md#store_attachment) | **POST** /v1/attachments | Store a new attachment.
|
|
||||||
[**update_attachment**](AttachmentsApi.md#update_attachment) | **PUT** /v1/attachments/{id} | Update existing attachment.
|
|
||||||
[**upload_attachment**](AttachmentsApi.md#upload_attachment) | **POST** /v1/attachments/{id}/upload | Upload an attachment.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## delete_attachment
|
|
||||||
|
|
||||||
> delete_attachment(id, x_trace_id)
|
|
||||||
Delete an attachment.
|
|
||||||
|
|
||||||
With this endpoint you delete an attachment, including any stored file data.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the single attachment. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
(empty response body)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## download_attachment
|
|
||||||
|
|
||||||
> std::path::PathBuf download_attachment(id, x_trace_id)
|
|
||||||
Download a single attachment.
|
|
||||||
|
|
||||||
This endpoint allows you to download the binary content of a transaction. It will be sent to you as a download, using the content type \"application/octet-stream\" and content disposition \"attachment; filename=example.pdf\".
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the attachment. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**std::path::PathBuf**](std::path::PathBuf.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/octet-stream, application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_attachment
|
|
||||||
|
|
||||||
> models::AttachmentSingle get_attachment(id, x_trace_id)
|
|
||||||
Get a single attachment.
|
|
||||||
|
|
||||||
Get a single attachment. This endpoint only returns the available metadata for the attachment. Actual file data is handled in two other endpoints (see below).
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the attachment. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AttachmentSingle**](AttachmentSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_attachment
|
|
||||||
|
|
||||||
> models::AttachmentArray list_attachment(x_trace_id, limit, page)
|
|
||||||
List all attachments.
|
|
||||||
|
|
||||||
This endpoint lists all attachments.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**limit** | Option<**i32**> | Number of items per page. The default pagination is per 50 items. | |
|
|
||||||
**page** | Option<**i32**> | Page number. The default pagination is per 50 items. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AttachmentArray**](AttachmentArray.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## store_attachment
|
|
||||||
|
|
||||||
> models::AttachmentSingle store_attachment(attachment_store, x_trace_id)
|
|
||||||
Store a new attachment.
|
|
||||||
|
|
||||||
Creates a new attachment. The data required can be submitted as a JSON body or as a list of parameters. You cannot use this endpoint to upload the actual file data (see below). This endpoint only creates the attachment object.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**attachment_store** | [**AttachmentStore**](AttachmentStore.md) | JSON array or key=value pairs with the necessary attachment information. See the model for the exact specifications. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AttachmentSingle**](AttachmentSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json, application/x-www-form-urlencoded
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## update_attachment
|
|
||||||
|
|
||||||
> models::AttachmentSingle update_attachment(id, attachment_update, x_trace_id)
|
|
||||||
Update existing attachment.
|
|
||||||
|
|
||||||
Update the meta data for an existing attachment. This endpoint does not allow you to upload or download data. For that, see below.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the attachment. | [required] |
|
|
||||||
**attachment_update** | [**AttachmentUpdate**](AttachmentUpdate.md) | JSON array with updated attachment information. See the model for the exact specifications. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AttachmentSingle**](AttachmentSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json, application/x-www-form-urlencoded
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## upload_attachment
|
|
||||||
|
|
||||||
> upload_attachment(id, x_trace_id, body)
|
|
||||||
Upload an attachment.
|
|
||||||
|
|
||||||
Use this endpoint to upload (and possible overwrite) the file contents of an attachment. Simply put the entire file in the body as binary data.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the attachment. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**body** | Option<**std::path::PathBuf**> | | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
(empty response body)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/octet-stream
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# AutoBudgetPeriod
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| Daily | daily |
|
|
||||||
| Weekly | weekly |
|
|
||||||
| Monthly | monthly |
|
|
||||||
| Quarterly | quarterly |
|
|
||||||
| HalfYear | half-year |
|
|
||||||
| Yearly | yearly |
|
|
||||||
| Null | null |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# AutoBudgetType
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| Reset | reset |
|
|
||||||
| Rollover | rollover |
|
|
||||||
| None | none |
|
|
||||||
| Null | null |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# AutocompleteAccount
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the account found by an auto-complete search. |
|
|
||||||
**name_with_balance** | **String** | Asset accounts and liabilities have a second field with the given date's account balance. |
|
|
||||||
**r#type** | **String** | Account type of the account found by the auto-complete search. |
|
|
||||||
**currency_id** | **String** | ID for the currency used by this account. |
|
|
||||||
**currency_name** | **String** | Currency name for the currency used by this account. |
|
|
||||||
**currency_code** | **String** | Currency code for the currency used by this account. |
|
|
||||||
**currency_symbol** | **String** | Currency symbol for the currency used by this account. |
|
|
||||||
**currency_decimal_places** | **i32** | Number of decimal places for the currency used by this account. |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,506 +0,0 @@
|
|||||||
# \AutocompleteApi
|
|
||||||
|
|
||||||
All URIs are relative to *https://demo.firefly-iii.org/api*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**get_accounts_ac**](AutocompleteApi.md#get_accounts_ac) | **GET** /v1/autocomplete/accounts | Returns all accounts of the user returned in a basic auto-complete array.
|
|
||||||
[**get_bills_ac**](AutocompleteApi.md#get_bills_ac) | **GET** /v1/autocomplete/bills | Returns all bills of the user returned in a basic auto-complete array.
|
|
||||||
[**get_budgets_ac**](AutocompleteApi.md#get_budgets_ac) | **GET** /v1/autocomplete/budgets | Returns all budgets of the user returned in a basic auto-complete array.
|
|
||||||
[**get_categories_ac**](AutocompleteApi.md#get_categories_ac) | **GET** /v1/autocomplete/categories | Returns all categories of the user returned in a basic auto-complete array.
|
|
||||||
[**get_currencies_ac**](AutocompleteApi.md#get_currencies_ac) | **GET** /v1/autocomplete/currencies | Returns all currencies of the user returned in a basic auto-complete array.
|
|
||||||
[**get_currencies_code_ac**](AutocompleteApi.md#get_currencies_code_ac) | **GET** /v1/autocomplete/currencies-with-code | Returns all currencies of the user returned in a basic auto-complete array. This endpoint is DEPRECATED and I suggest you DO NOT use it.
|
|
||||||
[**get_object_groups_ac**](AutocompleteApi.md#get_object_groups_ac) | **GET** /v1/autocomplete/object-groups | Returns all object groups of the user returned in a basic auto-complete array.
|
|
||||||
[**get_piggies_ac**](AutocompleteApi.md#get_piggies_ac) | **GET** /v1/autocomplete/piggy-banks | Returns all piggy banks of the user returned in a basic auto-complete array.
|
|
||||||
[**get_piggies_balance_ac**](AutocompleteApi.md#get_piggies_balance_ac) | **GET** /v1/autocomplete/piggy-banks-with-balance | Returns all piggy banks of the user returned in a basic auto-complete array complemented with balance information.
|
|
||||||
[**get_recurring_ac**](AutocompleteApi.md#get_recurring_ac) | **GET** /v1/autocomplete/recurring | Returns all recurring transactions of the user returned in a basic auto-complete array.
|
|
||||||
[**get_rule_groups_ac**](AutocompleteApi.md#get_rule_groups_ac) | **GET** /v1/autocomplete/rule-groups | Returns all rule groups of the user returned in a basic auto-complete array.
|
|
||||||
[**get_rules_ac**](AutocompleteApi.md#get_rules_ac) | **GET** /v1/autocomplete/rules | Returns all rules of the user returned in a basic auto-complete array.
|
|
||||||
[**get_tag_ac**](AutocompleteApi.md#get_tag_ac) | **GET** /v1/autocomplete/tags | Returns all tags of the user returned in a basic auto-complete array.
|
|
||||||
[**get_transaction_types_ac**](AutocompleteApi.md#get_transaction_types_ac) | **GET** /v1/autocomplete/transaction-types | Returns all transaction types returned in a basic auto-complete array. English only.
|
|
||||||
[**get_transactions_ac**](AutocompleteApi.md#get_transactions_ac) | **GET** /v1/autocomplete/transactions | Returns all transaction descriptions of the user returned in a basic auto-complete array.
|
|
||||||
[**get_transactions_idac**](AutocompleteApi.md#get_transactions_idac) | **GET** /v1/autocomplete/transactions-with-id | Returns all transactions, complemented with their ID, of the user returned in a basic auto-complete array. This endpoint is DEPRECATED and I suggest you DO NOT use it.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## get_accounts_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteAccount> get_accounts_ac(x_trace_id, query, limit, date, types)
|
|
||||||
Returns all accounts of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
**date** | Option<**String**> | If the account is an asset account or a liability, the autocomplete will also return the balance of the account on this date. | |
|
|
||||||
**types** | Option<[**Vec<models::AccountTypeFilter>**](models::AccountTypeFilter.md)> | Optional filter on the account type(s) used in the autocomplete. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteAccount>**](AutocompleteAccount.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_bills_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteBill> get_bills_ac(x_trace_id, query, limit)
|
|
||||||
Returns all bills of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteBill>**](AutocompleteBill.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_budgets_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteBudget> get_budgets_ac(x_trace_id, query, limit)
|
|
||||||
Returns all budgets of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteBudget>**](AutocompleteBudget.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_categories_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteCategory> get_categories_ac(x_trace_id, query, limit)
|
|
||||||
Returns all categories of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteCategory>**](AutocompleteCategory.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_currencies_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteCurrency> get_currencies_ac(x_trace_id, query, limit)
|
|
||||||
Returns all currencies of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteCurrency>**](AutocompleteCurrency.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_currencies_code_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteCurrencyCode> get_currencies_code_ac(x_trace_id, query, limit)
|
|
||||||
Returns all currencies of the user returned in a basic auto-complete array. This endpoint is DEPRECATED and I suggest you DO NOT use it.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteCurrencyCode>**](AutocompleteCurrencyCode.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_object_groups_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteObjectGroup> get_object_groups_ac(x_trace_id, query, limit)
|
|
||||||
Returns all object groups of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteObjectGroup>**](AutocompleteObjectGroup.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_piggies_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompletePiggy> get_piggies_ac(x_trace_id, query, limit)
|
|
||||||
Returns all piggy banks of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompletePiggy>**](AutocompletePiggy.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_piggies_balance_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompletePiggyBalance> get_piggies_balance_ac(x_trace_id, query, limit)
|
|
||||||
Returns all piggy banks of the user returned in a basic auto-complete array complemented with balance information.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompletePiggyBalance>**](AutocompletePiggyBalance.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_recurring_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteRecurrence> get_recurring_ac(x_trace_id, query, limit)
|
|
||||||
Returns all recurring transactions of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteRecurrence>**](AutocompleteRecurrence.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_rule_groups_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteRuleGroup> get_rule_groups_ac(x_trace_id, query, limit)
|
|
||||||
Returns all rule groups of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteRuleGroup>**](AutocompleteRuleGroup.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_rules_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteRule> get_rules_ac(x_trace_id, query, limit)
|
|
||||||
Returns all rules of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteRule>**](AutocompleteRule.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_tag_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteTag> get_tag_ac(x_trace_id, query, limit)
|
|
||||||
Returns all tags of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteTag>**](AutocompleteTag.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_transaction_types_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteTransactionType> get_transaction_types_ac(x_trace_id, query, limit)
|
|
||||||
Returns all transaction types returned in a basic auto-complete array. English only.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteTransactionType>**](AutocompleteTransactionType.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_transactions_ac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteTransaction> get_transactions_ac(x_trace_id, query, limit)
|
|
||||||
Returns all transaction descriptions of the user returned in a basic auto-complete array.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteTransaction>**](AutocompleteTransaction.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_transactions_idac
|
|
||||||
|
|
||||||
> Vec<models::AutocompleteTransactionId> get_transactions_idac(x_trace_id, query, limit)
|
|
||||||
Returns all transactions, complemented with their ID, of the user returned in a basic auto-complete array. This endpoint is DEPRECATED and I suggest you DO NOT use it.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**query** | Option<**String**> | The autocomplete search query. | |
|
|
||||||
**limit** | Option<**i32**> | The number of items returned. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**Vec<models::AutocompleteTransactionId>**](AutocompleteTransactionID.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AutocompleteBill
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the bill found by an auto-complete search. |
|
|
||||||
**active** | Option<**bool**> | Is the bill active or not? | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# AutocompleteBudget
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the budget found by an auto-complete search. |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# AutocompleteCategory
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the category found by an auto-complete search. |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# AutocompleteCurrency
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Currency name. |
|
|
||||||
**code** | **String** | Currency code. |
|
|
||||||
**symbol** | **String** | |
|
|
||||||
**decimal_places** | **i32** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# AutocompleteCurrencyCode
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Currency name with the code between brackets. |
|
|
||||||
**code** | **String** | Currency code. |
|
|
||||||
**symbol** | **String** | |
|
|
||||||
**decimal_places** | **i32** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AutocompleteObjectGroup
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**title** | **String** | Title of the object group found by an auto-complete search. |
|
|
||||||
**name** | **String** | Title of the object group found by an auto-complete search. |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# AutocompletePiggy
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the piggy bank found by an auto-complete search. |
|
|
||||||
**currency_id** | Option<**String**> | Currency ID for this piggy bank. | [optional]
|
|
||||||
**currency_code** | Option<**String**> | Currency code for this piggy bank. | [optional]
|
|
||||||
**currency_symbol** | Option<**String**> | | [optional]
|
|
||||||
**currency_name** | Option<**String**> | Currency name for the currency used by this account. | [optional]
|
|
||||||
**currency_decimal_places** | Option<**i32**> | | [optional]
|
|
||||||
**object_group_id** | Option<**String**> | The group ID of the group this object is part of. NULL if no group. | [optional]
|
|
||||||
**object_group_title** | Option<**String**> | The name of the group. NULL if no group. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# AutocompletePiggyBalance
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the piggy bank found by an auto-complete search. |
|
|
||||||
**name_with_balance** | Option<**String**> | Name of the piggy bank found by an auto-complete search with the current balance formatted nicely. | [optional]
|
|
||||||
**currency_id** | Option<**String**> | Currency ID for this piggy bank. | [optional]
|
|
||||||
**currency_code** | Option<**String**> | Currency code for this piggy bank. | [optional]
|
|
||||||
**currency_symbol** | Option<**String**> | | [optional]
|
|
||||||
**currency_decimal_places** | Option<**i32**> | | [optional]
|
|
||||||
**object_group_id** | Option<**String**> | The group ID of the group this object is part of. NULL if no group. | [optional]
|
|
||||||
**object_group_title** | Option<**String**> | The name of the group. NULL if no group. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AutocompleteRecurrence
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the recurrence found by an auto-complete search. |
|
|
||||||
**description** | Option<**String**> | Description of the recurrence found by auto-complete. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AutocompleteRule
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the rule found by an auto-complete search. |
|
|
||||||
**description** | Option<**String**> | Description of the rule found by auto-complete. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AutocompleteRuleGroup
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the rule group found by an auto-complete search. |
|
|
||||||
**description** | Option<**String**> | Description of the rule group found by auto-complete. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AutocompleteTag
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Name of the tag found by an auto-complete search. |
|
|
||||||
**tag** | **String** | Name of the tag found by an auto-complete search. |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# AutocompleteTransaction
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of a transaction journal (basically a single split). |
|
|
||||||
**transaction_group_id** | Option<**String**> | The ID of the underlying transaction group. | [optional]
|
|
||||||
**name** | **String** | Transaction description |
|
|
||||||
**description** | **String** | Transaction description |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# AutocompleteTransactionId
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of a transaction journal (basically a single split). |
|
|
||||||
**transaction_group_id** | Option<**String**> | The ID of the underlying transaction group. | [optional]
|
|
||||||
**name** | **String** | Transaction description with ID in the name. |
|
|
||||||
**description** | **String** | Transaction description with ID in the name. |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AutocompleteTransactionType
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
**name** | **String** | Type of the object found by an auto-complete search. |
|
|
||||||
**r#type** | **String** | Name of the object found by an auto-complete search. |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# AvailableBudget
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**created_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**updated_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**currency_id** | Option<**String**> | Use either currency_id or currency_code. | [optional]
|
|
||||||
**currency_code** | Option<**String**> | Use either currency_id or currency_code. | [optional]
|
|
||||||
**currency_symbol** | Option<**String**> | | [optional][readonly]
|
|
||||||
**currency_decimal_places** | Option<**i32**> | | [optional][readonly]
|
|
||||||
**amount** | **String** | |
|
|
||||||
**start** | **String** | Start date of the available budget. |
|
|
||||||
**end** | **String** | End date of the available budget. |
|
|
||||||
**spent_in_budgets** | Option<[**Vec<models::BudgetSpent>**](BudgetSpent.md)> | | [optional][readonly]
|
|
||||||
**spent_outside_budget** | Option<[**Vec<models::BudgetSpent>**](BudgetSpent.md)> | | [optional][readonly]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# AvailableBudgetArray
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**Vec<models::AvailableBudgetRead>**](AvailableBudgetRead.md) | |
|
|
||||||
**meta** | [**models::Meta**](Meta.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# AvailableBudgetRead
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**r#type** | **String** | Immutable value |
|
|
||||||
**id** | **String** | |
|
|
||||||
**attributes** | [**models::AvailableBudget**](AvailableBudget.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# AvailableBudgetSingle
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**models::AvailableBudgetRead**](AvailableBudgetRead.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# \AvailableBudgetsApi
|
|
||||||
|
|
||||||
All URIs are relative to *https://demo.firefly-iii.org/api*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**get_available_budget**](AvailableBudgetsApi.md#get_available_budget) | **GET** /v1/available-budgets/{id} | Get a single available budget.
|
|
||||||
[**list_available_budget**](AvailableBudgetsApi.md#list_available_budget) | **GET** /v1/available-budgets | List all available budget amounts.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## get_available_budget
|
|
||||||
|
|
||||||
> models::AvailableBudgetSingle get_available_budget(id, x_trace_id)
|
|
||||||
Get a single available budget.
|
|
||||||
|
|
||||||
Get a single available budget, by ID.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | The ID of the available budget. | [required] |
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AvailableBudgetSingle**](AvailableBudgetSingle.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_available_budget
|
|
||||||
|
|
||||||
> models::AvailableBudgetArray list_available_budget(x_trace_id, limit, page, start, end)
|
|
||||||
List all available budget amounts.
|
|
||||||
|
|
||||||
Firefly III allows users to set the amount that is available to be budgeted in so-called \"available budgets\". For example, the user could have 1200,- available to be divided during the coming month. This amount is used on the /budgets page. This endpoint returns all of these amounts and the periods for which they are set.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**x_trace_id** | Option<**uuid::Uuid**> | Unique identifier associated with this request. | |
|
|
||||||
**limit** | Option<**i32**> | Number of items per page. The default pagination is per 50 items. | |
|
|
||||||
**page** | Option<**i32**> | Page number. The default pagination is per 50 items. | |
|
|
||||||
**start** | Option<**String**> | A date formatted YYYY-MM-DD. | |
|
|
||||||
**end** | Option<**String**> | A date formatted YYYY-MM-DD. | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::AvailableBudgetArray**](AvailableBudgetArray.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[firefly_iii_auth](../README.md#firefly_iii_auth), [local_bearer_auth](../README.md#local_bearer_auth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json, application/vnd.api+json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# BadRequestResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**message** | Option<**String**> | | [optional]
|
|
||||||
**exception** | Option<**String**> | | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# BasicSummaryEntry
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**key** | Option<**String**> | This is a reference to the type of info shared, not influenced by translations or user preferences. The EUR value is a reference to the currency code. Possibilities are: balance-in-ABC, spent-in-ABC, earned-in-ABC, bills-paid-in-ABC, bills-unpaid-in-ABC, left-to-spend-in-ABC and net-worth-in-ABC. | [optional]
|
|
||||||
**title** | Option<**String**> | A translated title for the information shared. | [optional]
|
|
||||||
**monetary_value** | Option<**f64**> | The amount as a float. | [optional]
|
|
||||||
**currency_id** | Option<**String**> | The currency ID of the associated currency. | [optional]
|
|
||||||
**currency_code** | Option<**String**> | | [optional]
|
|
||||||
**currency_symbol** | Option<**String**> | | [optional]
|
|
||||||
**currency_decimal_places** | Option<**i32**> | Number of decimals for the associated currency. | [optional]
|
|
||||||
**value_parsed** | Option<**String**> | The amount formatted according to the users locale | [optional]
|
|
||||||
**local_icon** | Option<**String**> | Reference to a font-awesome icon without the fa- part. | [optional]
|
|
||||||
**sub_title** | Option<**String**> | A short explanation of the amounts origin. Already formatted according to the locale of the user or translated, if relevant. | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Bill
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**created_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**updated_at** | Option<**String**> | | [optional][readonly]
|
|
||||||
**currency_id** | Option<**String**> | Use either currency_id or currency_code | [optional]
|
|
||||||
**currency_code** | Option<**String**> | Use either currency_id or currency_code | [optional]
|
|
||||||
**currency_symbol** | Option<**String**> | | [optional][readonly]
|
|
||||||
**currency_decimal_places** | Option<**i32**> | | [optional][readonly]
|
|
||||||
**name** | **String** | |
|
|
||||||
**amount_min** | **String** | |
|
|
||||||
**amount_max** | **String** | |
|
|
||||||
**date** | **String** | |
|
|
||||||
**end_date** | Option<**String**> | The date after which this bill is no longer valid or applicable | [optional]
|
|
||||||
**extension_date** | Option<**String**> | The date before which the bill must be renewed (or cancelled) | [optional]
|
|
||||||
**repeat_freq** | [**models::BillRepeatFrequency**](BillRepeatFrequency.md) | |
|
|
||||||
**skip** | Option<**i32**> | How often the bill must be skipped. 1 means a bi-monthly bill. | [optional]
|
|
||||||
**active** | Option<**bool**> | If the bill is active. | [optional]
|
|
||||||
**order** | Option<**i32**> | Order of the bill. | [optional]
|
|
||||||
**notes** | Option<**String**> | | [optional]
|
|
||||||
**next_expected_match** | Option<**String**> | When the bill is expected to be due. | [optional][readonly]
|
|
||||||
**next_expected_match_diff** | Option<**String**> | Formatted (locally) when the bill is due. | [optional][readonly]
|
|
||||||
**object_group_id** | Option<**String**> | The group ID of the group this object is part of. NULL if no group. | [optional]
|
|
||||||
**object_group_order** | Option<**i32**> | The order of the group. At least 1, for the highest sorting. | [optional][readonly]
|
|
||||||
**object_group_title** | Option<**String**> | The name of the group. NULL if no group. | [optional]
|
|
||||||
**pay_dates** | Option<**Vec<String>**> | Array of future dates when the bill is expected to be paid. Autogenerated. | [optional][readonly]
|
|
||||||
**paid_dates** | Option<[**Vec<models::BillPaidDatesInner>**](Bill_paid_dates_inner.md)> | Array of past transactions when the bill was paid. | [optional][readonly]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# BillArray
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**Vec<models::BillRead>**](BillRead.md) | |
|
|
||||||
**meta** | [**models::Meta**](Meta.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# BillPaidDatesInner
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**transaction_group_id** | Option<**String**> | Transaction group ID of the paid bill. | [optional][readonly]
|
|
||||||
**transaction_journal_id** | Option<**String**> | Transaction journal ID of the paid bill. | [optional][readonly]
|
|
||||||
**date** | Option<**String**> | Date the bill was paid. | [optional][readonly]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# BillRead
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**r#type** | **String** | Immutable value |
|
|
||||||
**id** | **String** | |
|
|
||||||
**attributes** | [**models::Bill**](Bill.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# BillRepeatFrequency
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Value |
|
|
||||||
|---- | -----|
|
|
||||||
| Weekly | weekly |
|
|
||||||
| Monthly | monthly |
|
|
||||||
| Quarterly | quarterly |
|
|
||||||
| HalfYear | half-year |
|
|
||||||
| Yearly | yearly |
|
|
||||||
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# BillSingle
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**models::BillRead**](BillRead.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user