diff --git a/moneymgr_backend/Cargo.lock b/moneymgr_backend/Cargo.lock index fd064bb..623444a 100644 --- a/moneymgr_backend/Cargo.lock +++ b/moneymgr_backend/Cargo.lock @@ -341,6 +341,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.18" @@ -623,6 +638,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1610,6 +1639,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -2093,6 +2146,7 @@ dependencies = [ "actix-session", "actix-web", "anyhow", + "chrono", "clap", "diesel", "diesel_migrations", @@ -3731,6 +3785,41 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "windows-core" +version = "0.60.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.1.0" diff --git a/moneymgr_backend/Cargo.toml b/moneymgr_backend/Cargo.toml index 1240ce1..d014640 100644 --- a/moneymgr_backend/Cargo.toml +++ b/moneymgr_backend/Cargo.toml @@ -31,3 +31,4 @@ mime_guess = "2.0.5" rust-embed = { version = "8.6.0" } sha2 = "0.10.8" httpdate = "1.0.3" +chrono = "0.4.41" diff --git a/moneymgr_backend/src/controllers/backup_controller.rs b/moneymgr_backend/src/controllers/backup_controller.rs index 7b15192..4e98a98 100644 --- a/moneymgr_backend/src/controllers/backup_controller.rs +++ b/moneymgr_backend/src/controllers/backup_controller.rs @@ -1,15 +1,18 @@ use crate::controllers::HttpResult; -use crate::converters::finances_manager_converter::FinancesManagerFile; +use crate::converters::finances_manager_converter::{ + FinancesManagerAccount, FinancesManagerFile, FinancesManagerMovement, +}; use crate::extractors::auth_extractor::AuthExtractor; use crate::extractors::file_extractor::FileExtractor; use crate::models::accounts::AccountType; use crate::services::accounts_service::UpdateAccountQuery; use crate::services::movements_service::UpdateMovementQuery; use crate::services::{accounts_service, movements_service}; +use crate::utils::time_utils::{format_date, time}; use actix_web::HttpResponse; /// Import data from a [FinancesManager](https://gitlab.com/pierre42100/cpp-financesmanager) file -pub async fn import_financesmanager(auth: AuthExtractor, file: FileExtractor) -> HttpResult { +pub async fn finances_manager_import(auth: AuthExtractor, file: FileExtractor) -> HttpResult { let file = FinancesManagerFile::parse(&String::from_utf8_lossy(&file.buff))?; // Create each account & push the movements independently @@ -38,3 +41,38 @@ pub async fn import_financesmanager(auth: AuthExtractor, file: FileExtractor) -> Ok(HttpResponse::Accepted().finish()) } + +/// Export data to a [FinancesManager](https://gitlab.com/pierre42100/cpp-financesmanager) file +pub async fn finances_manager_export(auth: AuthExtractor) -> HttpResult { + let accounts = accounts_service::get_list_user(auth.user_id()).await?; + + let mut out = FinancesManagerFile { accounts: vec![] }; + + for account in accounts { + let movements = movements_service::get_list_account(account.id()).await?; + let mut file_account = FinancesManagerAccount { + name: account.name, + movements: Vec::with_capacity(movements.len()), + }; + + for movement in movements { + file_account.movements.push(FinancesManagerMovement { + label: movement.label, + time: movement.time as u64, + amount: movement.amount, + }); + } + + out.accounts.push(file_account); + } + + Ok(HttpResponse::Ok() + .insert_header(( + "Content-Disposition", + format!( + "attachment; filename=export_{}.finance", + format_date(time() as i64)?.replace('/', "-") + ), + )) + .body(out.encode())) +} diff --git a/moneymgr_backend/src/main.rs b/moneymgr_backend/src/main.rs index bbc6adf..65fd4d1 100644 --- a/moneymgr_backend/src/main.rs +++ b/moneymgr_backend/src/main.rs @@ -163,8 +163,12 @@ async fn main() -> std::io::Result<()> { ) // Backup controller .route( - "/api/backup/financesmanager/import", - web::post().to(backup_controller::import_financesmanager), + "/api/backup/finances_manager/import", + web::post().to(backup_controller::finances_manager_import), + ) + .route( + "/api/backup/finances_manager/export", + web::get().to(backup_controller::finances_manager_export), ) // Static assets .route("/", web::get().to(static_controller::root_index)) diff --git a/moneymgr_backend/src/utils/time_utils.rs b/moneymgr_backend/src/utils/time_utils.rs index 9c2a441..71dcc4c 100644 --- a/moneymgr_backend/src/utils/time_utils.rs +++ b/moneymgr_backend/src/utils/time_utils.rs @@ -1,5 +1,6 @@ //! # Time utilities +use chrono::Datelike; use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// Get the current time since epoch @@ -19,3 +20,15 @@ pub fn unix_to_system_time(time: u64) -> SystemTime { pub fn unix_to_http_date(time: u64) -> String { httpdate::fmt_http_date(unix_to_system_time(time)) } + +/// Format given UNIX time in a simple format +pub fn format_date(time: i64) -> anyhow::Result { + let date = chrono::DateTime::from_timestamp(time, 0).ok_or(anyhow::anyhow!("invalid date"))?; + + Ok(format!( + "{:0>2}/{:0>2}/{}", + date.day(), + date.month(), + date.year() + )) +}