Can export data as ZIP file

This commit is contained in:
2025-05-02 23:24:38 +02:00
parent 1b3ce1a98d
commit f335b9d0c0
6 changed files with 321 additions and 8 deletions

View File

@ -53,3 +53,21 @@ pub const ACCOUNT_TYPES: [AccountTypeDesc; 3] = [
icon: include_str!("../assets/saving.svg"),
},
];
/// ZIP export paths
pub mod zip_export {
/// Accounts file path inside archive
pub const ACCOUNTS_FILE: &str = "accounts.json";
/// Movements file path inside archive
pub const MOVEMENTS_FILE: &str = "movements.json";
/// Files list file path inside archive
pub const FILES_FILE: &str = "files.json";
/// Files directory inside archive
pub const FILES_DIR: &str = "files/";
/// Inbox file path inside archive
pub const INBOX_FILE: &str = "inbox.json";
}

View File

@ -1,3 +1,5 @@
use crate::app_config::AppConfig;
use crate::constants;
use crate::controllers::HttpResult;
use crate::converters::finances_manager_converter::{
FinancesManagerAccount, FinancesManagerFile, FinancesManagerMovement,
@ -7,9 +9,23 @@ 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::services::{accounts_service, files_service, movements_service};
use crate::utils::time_utils::{format_date, time};
use actix_web::HttpResponse;
use actix_files::NamedFile;
use actix_web::{HttpRequest, HttpResponse};
use serde::Serialize;
use std::fs::File;
use std::io::Write;
use zip::ZipWriter;
use zip::write::SimpleFileOptions;
/// Generate export filename
fn export_filename(ext: &str) -> String {
format!(
"export_{}.{ext}",
format_date(time() as i64).unwrap().replace('/', "-")
)
}
/// Import data from a [FinancesManager](https://gitlab.com/pierre42100/cpp-financesmanager) file
pub async fn finances_manager_import(auth: AuthExtractor, file: FileExtractor) -> HttpResult {
@ -69,10 +85,71 @@ pub async fn finances_manager_export(auth: AuthExtractor) -> HttpResult {
Ok(HttpResponse::Ok()
.insert_header((
"Content-Disposition",
format!(
"attachment; filename=export_{}.finance",
format_date(time() as i64)?.replace('/', "-")
),
format!("attachment; filename={}", export_filename("finance")),
))
.body(out.encode()))
}
/// Add JSON file to ZIP
fn zip_json<E: Serialize>(
zip: &mut ZipWriter<File>,
path: &str,
content: &E,
) -> anyhow::Result<()> {
let file_encoded = serde_json::to_string(&content)?;
let options = SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated)
.unix_permissions(0o750);
zip.start_file(path, options)?;
zip.write_all(file_encoded.as_bytes())?;
Ok(())
}
/// Export all user data as ZIP
pub async fn zip_export(req: HttpRequest, auth: AuthExtractor) -> HttpResult {
let dest_dir = tempfile::tempdir_in(&AppConfig::get().temp_dir)?;
let zip_path = dest_dir.path().join("export.zip");
let file = File::create(&zip_path)?;
let mut zip = ZipWriter::new(file);
// Process all JSON documents
zip_json(
&mut zip,
constants::zip_export::ACCOUNTS_FILE,
&accounts_service::get_list_user(auth.user_id()).await?,
)?;
zip_json(
&mut zip,
constants::zip_export::MOVEMENTS_FILE,
&movements_service::get_all_movements_user(auth.user_id()).await?,
)?;
let files_list = files_service::get_all_files_user(auth.user_id()).await?;
zip_json(&mut zip, constants::zip_export::FILES_FILE, &files_list)?;
// TODO : inbox
// Process all files
for file in files_list {
let buff = files_service::get_file_content(&file).await?;
let options = SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated)
.unix_permissions(0o750);
zip.start_file(
format!("{}{}", constants::zip_export::FILES_DIR, file.sha512),
options,
)?;
zip.write_all(&buff)?;
}
// Finalize ZIP and return response
zip.finish()?;
let file = File::open(zip_path)?;
let file = NamedFile::from_file(file, export_filename("zip"))?;
Ok(file.into_response(&req))
}

View File

@ -1,6 +1,7 @@
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, ResponseError};
use std::error::Error;
use zip::result::ZipError;
pub mod accounts_controller;
pub mod auth_controller;
@ -30,6 +31,10 @@ pub enum HttpFailure {
InternalError(#[from] anyhow::Error),
#[error("a serde_json error occurred: {0}")]
SerdeJsonError(#[from] serde_json::error::Error),
#[error("a zip manipulation error occurred: {0}")]
ZipError(#[from] ZipError),
#[error("a standard I/O manipulation error occurred: {0}")]
StdIoError(#[from] std::io::Error),
}
impl ResponseError for HttpFailure {

View File

@ -170,6 +170,10 @@ async fn main() -> std::io::Result<()> {
"/api/backup/finances_manager/export",
web::get().to(backup_controller::finances_manager_export),
)
.route(
"/api/backup/zip/export",
web::get().to(backup_controller::zip_export),
)
// Static assets
.route("/", web::get().to(static_controller::root_index))
.route(