Can import data from FinancesManager
This commit is contained in:
		
							
								
								
									
										40
									
								
								moneymgr_backend/src/controllers/backup_controller.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								moneymgr_backend/src/controllers/backup_controller.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
use crate::controllers::HttpResult;
 | 
			
		||||
use crate::converters::finances_manager_converter::FinancesManagerFile;
 | 
			
		||||
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 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 {
 | 
			
		||||
    let file = FinancesManagerFile::parse(&String::from_utf8_lossy(&file.buff))?;
 | 
			
		||||
 | 
			
		||||
    // Create each account & push the movements independently
 | 
			
		||||
    for file_account in file.accounts {
 | 
			
		||||
        let account = accounts_service::create(
 | 
			
		||||
            auth.user_id(),
 | 
			
		||||
            &UpdateAccountQuery {
 | 
			
		||||
                name: file_account.name,
 | 
			
		||||
                r#type: AccountType::Cash,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
        for file_movement in file_account.movements {
 | 
			
		||||
            movements_service::create(&UpdateMovementQuery {
 | 
			
		||||
                account_id: account.id(),
 | 
			
		||||
                time: file_movement.time,
 | 
			
		||||
                label: file_movement.label,
 | 
			
		||||
                file_id: None,
 | 
			
		||||
                amount: file_movement.amount,
 | 
			
		||||
                checked: false,
 | 
			
		||||
            })
 | 
			
		||||
            .await?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Accepted().finish())
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@ use std::error::Error;
 | 
			
		||||
 | 
			
		||||
pub mod accounts_controller;
 | 
			
		||||
pub mod auth_controller;
 | 
			
		||||
pub mod backup_controller;
 | 
			
		||||
pub mod files_controller;
 | 
			
		||||
pub mod movement_controller;
 | 
			
		||||
pub mod server_controller;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								moneymgr_backend/src/converters.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								moneymgr_backend/src/converters.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pub mod finances_manager_converter;
 | 
			
		||||
							
								
								
									
										107
									
								
								moneymgr_backend/src/converters/finances_manager_converter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								moneymgr_backend/src/converters/finances_manager_converter.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
use std::num::{ParseFloatError, ParseIntError};
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
enum FinancesManagerDecodeError {
 | 
			
		||||
    #[error("Movement entry is not a three-part component! got {0} parts instead of 3!")]
 | 
			
		||||
    MovementParts(usize),
 | 
			
		||||
    #[error("Could not decode time!")]
 | 
			
		||||
    Time(ParseIntError),
 | 
			
		||||
    #[error("Could not decode amount!")]
 | 
			
		||||
    Amount(ParseFloatError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, serde::Serialize)]
 | 
			
		||||
pub struct FinancesManagerMovement {
 | 
			
		||||
    pub label: String,
 | 
			
		||||
    pub time: u64,
 | 
			
		||||
    pub amount: f32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, serde::Serialize)]
 | 
			
		||||
pub struct FinancesManagerAccount {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub movements: Vec<FinancesManagerMovement>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, serde::Serialize)]
 | 
			
		||||
pub struct FinancesManagerFile {
 | 
			
		||||
    pub accounts: Vec<FinancesManagerAccount>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FinancesManagerFile {
 | 
			
		||||
    /// Parse a finance manager file
 | 
			
		||||
    pub fn parse(file: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        let mut res = Self { accounts: vec![] };
 | 
			
		||||
 | 
			
		||||
        let mut curr_account = None;
 | 
			
		||||
 | 
			
		||||
        for l in file.lines() {
 | 
			
		||||
            // Check if we reached the end of an account
 | 
			
		||||
            if l.trim().is_empty() {
 | 
			
		||||
                if let Some(a) = curr_account {
 | 
			
		||||
                    res.accounts.push(a);
 | 
			
		||||
                }
 | 
			
		||||
                curr_account = None;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if l == "==============" {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            match &mut curr_account {
 | 
			
		||||
                // Header
 | 
			
		||||
                None => {
 | 
			
		||||
                    curr_account = Some(FinancesManagerAccount {
 | 
			
		||||
                        name: l.to_string(),
 | 
			
		||||
                        movements: vec![],
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Account content
 | 
			
		||||
                Some(account) => {
 | 
			
		||||
                    let split = l.split(';').collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
                    if split.len() != 3 {
 | 
			
		||||
                        return Err(FinancesManagerDecodeError::MovementParts(split.len()).into());
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    account.movements.push(FinancesManagerMovement {
 | 
			
		||||
                        label: split[1].to_string(),
 | 
			
		||||
                        time: split[0]
 | 
			
		||||
                            .parse::<u64>()
 | 
			
		||||
                            .map_err(FinancesManagerDecodeError::Time)?,
 | 
			
		||||
                        amount: split[2]
 | 
			
		||||
                            .parse::<f32>()
 | 
			
		||||
                            .map_err(FinancesManagerDecodeError::Amount)?,
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Push last account
 | 
			
		||||
 | 
			
		||||
        Ok(res)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Encode FinancesManager file
 | 
			
		||||
    pub fn encode(&self) -> String {
 | 
			
		||||
        let mut out = String::new();
 | 
			
		||||
 | 
			
		||||
        for account in &self.accounts {
 | 
			
		||||
            out.push_str(&account.name);
 | 
			
		||||
            out.push_str("\n==============\n");
 | 
			
		||||
            for movement in &account.movements {
 | 
			
		||||
                out.push_str(&format!(
 | 
			
		||||
                    "{};{};{}\n",
 | 
			
		||||
                    movement.time,
 | 
			
		||||
                    movement.label.replace(';', ","),
 | 
			
		||||
                    movement.amount
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            out.push_str("\n\n\n");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        out
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,7 @@ pub mod app_config;
 | 
			
		||||
pub mod connections;
 | 
			
		||||
pub mod constants;
 | 
			
		||||
pub mod controllers;
 | 
			
		||||
pub mod converters;
 | 
			
		||||
pub mod extractors;
 | 
			
		||||
pub mod models;
 | 
			
		||||
pub mod routines;
 | 
			
		||||
 
 | 
			
		||||
@@ -161,6 +161,11 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
                "/api/stats/balance_variation",
 | 
			
		||||
                web::get().to(stats_controller::balance_variation),
 | 
			
		||||
            )
 | 
			
		||||
            // Backup controller
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/backup/financesmanager/import",
 | 
			
		||||
                web::post().to(backup_controller::import_financesmanager),
 | 
			
		||||
            )
 | 
			
		||||
            // Static assets
 | 
			
		||||
            .route("/", web::get().to(static_controller::root_index))
 | 
			
		||||
            .route(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user