use anyhow::Result; use sqlx::PgPool; use uuid::Uuid; use crate::models::ExportFormat; use crate::service::add::{AddParams, run as add_run}; use crate::service::export::{build_meta_entries, build_secret_entries}; #[derive(Debug, serde::Serialize)] pub struct ImportSummary { pub total: usize, pub inserted: usize, pub skipped: usize, pub failed: usize, pub dry_run: bool, } pub struct ImportParams<'a> { pub file: &'a str, pub force: bool, pub dry_run: bool, pub user_id: Option, } pub async fn run( pool: &PgPool, params: ImportParams<'_>, master_key: &[u8; 32], ) -> Result { let format = ExportFormat::from_extension(params.file)?; let content = std::fs::read_to_string(params.file) .map_err(|e| anyhow::anyhow!("Cannot read file '{}': {}", params.file, e))?; let data = format.deserialize(&content)?; if data.version != 1 { anyhow::bail!( "Unsupported export version {}. Only version 1 is supported.", data.version ); } let total = data.entries.len(); let mut inserted = 0usize; let mut skipped = 0usize; let mut failed = 0usize; for entry in &data.entries { let exists: bool = sqlx::query_scalar( "SELECT EXISTS(SELECT 1 FROM entries \ WHERE folder = $1 AND name = $2 AND user_id IS NOT DISTINCT FROM $3)", ) .bind(&entry.folder) .bind(&entry.name) .bind(params.user_id) .fetch_one(pool) .await .unwrap_or(false); if exists && !params.force { return Err(anyhow::anyhow!( "Import aborted: conflict on '{}'", entry.name )); } if params.dry_run { if exists { skipped += 1; } else { inserted += 1; } continue; } let secret_entries = build_secret_entries(entry.secrets.as_ref()); let meta_entries = build_meta_entries(&entry.metadata); match add_run( pool, AddParams { name: &entry.name, folder: &entry.folder, entry_type: &entry.entry_type, notes: &entry.notes, tags: &entry.tags, meta_entries: &meta_entries, secret_entries: &secret_entries, link_secret_names: &[], user_id: params.user_id, }, master_key, ) .await { Ok(_) => { inserted += 1; } Err(e) => { tracing::error!( name = entry.name, error = %e, "failed to import entry" ); failed += 1; } } } if failed > 0 { return Err(anyhow::anyhow!("{} record(s) failed to import", failed)); } Ok(ImportSummary { total, inserted, skipped, failed, dry_run: params.dry_run, }) }