Can import ZIP
This commit is contained in:
@ -6,7 +6,10 @@ use crate::converters::finances_manager_converter::{
|
||||
};
|
||||
use crate::extractors::auth_extractor::AuthExtractor;
|
||||
use crate::extractors::file_extractor::FileExtractor;
|
||||
use crate::models::accounts::AccountType;
|
||||
use crate::models::accounts::{Account, AccountID, AccountType};
|
||||
use crate::models::files;
|
||||
use crate::models::files::FileID;
|
||||
use crate::models::movements::Movement;
|
||||
use crate::services::accounts_service::UpdateAccountQuery;
|
||||
use crate::services::movements_service::UpdateMovementQuery;
|
||||
use crate::services::{accounts_service, files_service, movements_service};
|
||||
@ -14,10 +17,20 @@ use crate::utils::time_utils::{format_date, time};
|
||||
use actix_files::NamedFile;
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use zip::ZipWriter;
|
||||
use std::io::{Cursor, Read, Seek, Write};
|
||||
use zip::write::SimpleFileOptions;
|
||||
use zip::{ZipArchive, ZipWriter};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum BackupControllerError {
|
||||
#[error("The account with id {0:?} does not exists!")]
|
||||
NonexistentAccountId(AccountID),
|
||||
#[error("The file with id {0:?} does not exists!")]
|
||||
NonexistentFileId(FileID),
|
||||
}
|
||||
|
||||
/// Generate export filename
|
||||
fn export_filename(ext: &str) -> String {
|
||||
@ -153,3 +166,94 @@ pub async fn zip_export(req: HttpRequest, auth: AuthExtractor) -> HttpResult {
|
||||
|
||||
Ok(file.into_response(&req))
|
||||
}
|
||||
|
||||
/// Read JSON from archive
|
||||
fn unzip_json<E: DeserializeOwned, R: Read + Seek>(
|
||||
zip: &mut ZipArchive<R>,
|
||||
path: &str,
|
||||
) -> anyhow::Result<E> {
|
||||
let mut file = zip.by_name(path)?;
|
||||
let mut content = String::with_capacity(file.size() as usize);
|
||||
file.read_to_string(&mut content)?;
|
||||
|
||||
Ok(serde_json::from_str(&content)?)
|
||||
}
|
||||
|
||||
/// Replace all data with data included in ZIP file
|
||||
pub async fn zip_import(auth: AuthExtractor, file: FileExtractor) -> HttpResult {
|
||||
// Parse provided files
|
||||
let zip_cursor = Cursor::new(file.buff);
|
||||
let mut zip = ZipArchive::new(zip_cursor)?;
|
||||
|
||||
let new_accounts: Vec<Account> = unzip_json(&mut zip, constants::zip_export::ACCOUNTS_FILE)?;
|
||||
let new_movements: Vec<Movement> = unzip_json(&mut zip, constants::zip_export::MOVEMENTS_FILE)?;
|
||||
let new_files: Vec<files::File> = unzip_json(&mut zip, constants::zip_export::FILES_FILE)?;
|
||||
// TODO : inbox
|
||||
|
||||
// Delete all data
|
||||
accounts_service::delete_all_user(auth.user_id()).await?;
|
||||
files_service::delete_all_user(auth.user_id()).await?;
|
||||
|
||||
// Create the files
|
||||
let mut files_mapping = HashMap::new();
|
||||
for file in new_files {
|
||||
let mut zip_file = zip
|
||||
.by_name(&format!(
|
||||
"{}{}",
|
||||
constants::zip_export::FILES_DIR,
|
||||
file.sha512
|
||||
))
|
||||
.map_err(|e| anyhow::anyhow!("Could not find file with hash {}: {}", file.sha512, e))?;
|
||||
let mut file_buff = Vec::with_capacity(zip_file.size() as usize);
|
||||
zip_file.read_to_end(&mut file_buff)?;
|
||||
let created_file =
|
||||
files_service::create_file_with_file_name(auth.user_id(), &file.file_name, &file_buff)
|
||||
.await?;
|
||||
files_mapping.insert(file.id(), created_file);
|
||||
}
|
||||
|
||||
// Create the accounts
|
||||
let mut accounts_mapping = HashMap::new();
|
||||
for account in new_accounts {
|
||||
let created_account = accounts_service::create(
|
||||
auth.user_id(),
|
||||
&UpdateAccountQuery {
|
||||
name: account.name.to_string(),
|
||||
r#type: account.account_type(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if account.default_account {
|
||||
accounts_service::set_default(auth.user_id(), created_account.id()).await?;
|
||||
}
|
||||
|
||||
accounts_mapping.insert(account.id(), created_account);
|
||||
}
|
||||
|
||||
// Create the movements
|
||||
for movement in new_movements {
|
||||
movements_service::create(&UpdateMovementQuery {
|
||||
account_id: accounts_mapping
|
||||
.get(&movement.account_id())
|
||||
.ok_or_else(|| BackupControllerError::NonexistentAccountId(movement.account_id()))?
|
||||
.id(),
|
||||
time: movement.time as u64,
|
||||
label: movement.label.to_string(),
|
||||
file_id: movement
|
||||
.file_id()
|
||||
.map(|file_id| {
|
||||
files_mapping
|
||||
.get(&file_id)
|
||||
.ok_or(BackupControllerError::NonexistentFileId(file_id))
|
||||
})
|
||||
.transpose()?
|
||||
.map(|f| f.id()),
|
||||
amount: movement.amount,
|
||||
checked: movement.checked,
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Accepted().finish())
|
||||
}
|
||||
|
Reference in New Issue
Block a user