diff --git a/src/firefly.rs b/src/firefly.rs index 34f7c1c..4d1add7 100644 --- a/src/firefly.rs +++ b/src/firefly.rs @@ -1,6 +1,7 @@ use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::Client; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use tracing::{error, info}; pub struct Firefly { url: String, @@ -9,9 +10,11 @@ pub struct Firefly { #[derive(Deserialize)] pub struct TransactionSplit { + pub transaction_journal_id: String, pub date: chrono::DateTime, pub amount: String, pub description: String, + pub notes: Option, } #[derive(Deserialize)] @@ -36,6 +39,57 @@ pub struct TransactionSingle { pub data: TransactionRead, } +#[derive(Serialize)] +pub struct TransactionSplitUpdate { + pub transaction_journal_id: String, + pub amount: String, + pub notes: Option, +} + +#[derive(Serialize)] +pub struct TransactionUpdate { + pub transactions: Vec, +} + +#[derive(Serialize)] +#[non_exhaustive] +enum AttachableType { + TransactionJournal, +} + +#[derive(Serialize)] +struct AttachmentStore { + filename: String, + attachable_type: AttachableType, + attachable_id: String, +} + +#[derive(Deserialize)] +struct AttachmentRead { + id: String, +} + +#[derive(Deserialize)] +struct AttachmentSingle { + data: AttachmentRead, +} + +impl TransactionRead { + pub fn amount(&self) -> f64 { + self.attributes + .transactions + .iter() + .filter_map(|t| match t.amount.parse::() { + Ok(v) => Some(v), + Err(e) => { + error!("Invalid amount: {}", e); + None + }, + }) + .sum() + } +} + impl Firefly { pub fn new(url: impl Into, token: &str) -> Self { let mut headers = HeaderMap::new(); @@ -83,4 +137,49 @@ impl Firefly { res.error_for_status_ref()?; res.json().await } + + pub async fn update_transaction( + &self, + id: &str, + txn: &TransactionUpdate, + ) -> Result { + let url = format!("{}/api/v1/transactions/{}", self.url, id); + let res = self.client.put(url).json(txn).send().await?; + res.error_for_status_ref()?; + info!("Successfully updated transaction {}", id); + res.json().await + } + + pub async fn attach_receipt( + &self, + id: &str, + content: impl Into, + filename: impl Into, + ) -> Result<(), reqwest::Error> { + let filename = filename.into(); + info!("Attaching receipt {} to transaction {}", filename, id); + let url = format!("{}/api/v1/attachments", self.url); + let data = AttachmentStore { + filename, + attachable_type: AttachableType::TransactionJournal, + attachable_id: id.into(), + }; + let res = self.client.post(url).json(&data).send().await?; + res.error_for_status_ref()?; + let attachment: AttachmentSingle = res.json().await?; + info!("Created attachment {}", attachment.data.id); + let url = format!( + "{}/api/v1/attachments/{}/upload", + self.url, attachment.data.id + ); + let res = self + .client + .post(url) + .header("Content-Type", "application/octet-stream") + .body(content) + .send() + .await?; + res.error_for_status_ref()?; + Ok(()) + } } diff --git a/src/main.rs b/src/main.rs index 2c3b791..e1408eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,13 +7,16 @@ use rocket::form::Form; use rocket::fs::{FileServer, TempFile}; use rocket::http::Status; use rocket::response::Redirect; +use rocket::tokio::io::{AsyncReadExt, BufReader}; use rocket::State; use rocket_dyn_templates::{context, Template}; use serde::Serialize; use tracing::{debug, error}; use config::{Config, ConfigError}; -use firefly::{Firefly, TransactionRead}; +use firefly::{ + Firefly, TransactionRead, TransactionSplitUpdate, TransactionUpdate, +}; struct Context { #[allow(dead_code)] @@ -47,7 +50,7 @@ pub struct Transaction { #[derive(rocket::FromForm)] struct TransactionPostData<'r> { - amount: f32, + amount: f64, notes: String, photo: Vec>, } @@ -64,18 +67,7 @@ impl TryFrom for Transaction { }, }; let date = first_split.date; - let amount = t - .attributes - .transactions - .iter() - .filter_map(|t| match t.amount.parse::() { - Ok(v) => Some(v), - Err(e) => { - error!("Invalid amount: {}", e); - None - }, - }) - .sum(); + let amount = t.amount(); let description = if let Some(title) = &t.attributes.group_title { title.into() } else { @@ -158,11 +150,72 @@ async fn get_transaction(id: &str, ctx: &State) -> Option