use anyhow::{Context, Result};
use colored::Colorize;
use log::error;
use std::path::PathBuf;
use std::str::FromStr;
use crate::config::{Environment, MongoConfig};
use crate::utils::mongodb;
pub struct SyncOptions {
pub create_backup: bool,
pub drop_collections: bool,
pub clear_collections: bool,
}
impl Default for SyncOptions {
fn default() -> Self {
Self {
create_backup: true,
drop_collections: true,
clear_collections: false,
}
}
}
impl SyncOptions {
pub fn update_collection_settings(&mut self) {
if self.drop_collections {
self.clear_collections = false;
}
}
}
pub struct SyncConfig {
pub source_env: Environment,
pub target_env: Environment,
pub source_db: String,
pub target_db: String,
pub options: SyncOptions,
}
pub fn parse_environment(env_str: &str) -> Result<Environment> {
Environment::from_str(env_str).context(format!("Invalid environment: {}", env_str))
}
pub async fn get_databases(env: &Environment) -> Result<Vec<String>> {
let config = MongoConfig::from_env(env.clone())
.context(format!("Failed to get configuration for {}", env))?;
let all_dbs = mongodb::list_databases(&config).await?;
let dbs = all_dbs
.into_iter()
.filter(|db| !matches!(db.as_str(), "admin" | "local" | "config"))
.collect();
Ok(dbs)
}
pub async fn perform_sync(config: SyncConfig) -> Result<()> {
let source_config = MongoConfig::from_env(config.source_env.clone()).context(format!(
"Failed to get configuration for {}",
config.source_env
))?;
let target_config = MongoConfig::from_env(config.target_env.clone()).context(format!(
"Failed to get configuration for {}",
config.target_env
))?;
println!("\n{}", "Synchronization plan:".bold().underline());
println!("{} {}", "From:".green().bold(), config.source_env);
println!("{} {}", "To:".green().bold(), config.target_env);
println!("{} {}", "Source database:".green().bold(), config.source_db);
println!("{} {}", "Target database:".green().bold(), config.target_db);
println!(
"{} {}",
"Create backup:".green().bold(),
if config.options.create_backup {
"Yes"
} else {
"No"
}
);
println!(
"{} {}",
"Drop collections:".green().bold(),
if config.options.drop_collections {
"Yes"
} else {
"No"
}
);
println!(
"{} {}",
"Clear collections:".green().bold(),
if config.options.clear_collections {
"Yes"
} else {
"No"
}
);
perform_sync_single(
&source_config,
&target_config,
&config.source_db,
&config.target_db,
config.options.create_backup,
config.options.drop_collections,
config.options.clear_collections,
)
.await
}
async fn perform_sync_single(
source_config: &MongoConfig,
target_config: &MongoConfig,
source_db: &str,
target_db: &str,
should_backup: bool,
drop_collections: bool,
clear_collections: bool,
) -> Result<()> {
let temp_dir = tempfile::tempdir().context("Failed to create temporary directory")?;
let temp_path = temp_dir.path();
println!("\nProcessing database: {}", source_db);
let mut backup_path: Option<PathBuf> = None;
if should_backup {
match mongodb::create_backup(target_config, target_db).await {
Ok(path) => {
let path_display = path.display().to_string();
backup_path = Some(path);
println!("{} {}", "Backup created:".green(), path_display);
}
Err(e) => {
error!("Failed to create backup: {}", e);
println!(
"{} Failed to create backup, proceeding without backup",
"Warning:".yellow().bold()
);
}
}
}
match mongodb::export_database(source_config, source_db, temp_path).await {
Ok(_) => {
println!("{} {}", "Export completed:".green(), source_db);
let export_db_path = temp_path.join(source_db);
if !export_db_path.exists() {
error!(
"Export directory not found at expected path: {}",
export_db_path.display()
);
println!(
"{} Export directory not found at: {}",
"Error:".red().bold(),
export_db_path.display()
);
return Ok(());
}
if source_db != target_db {
let target_db_path = temp_path.join(target_db);
if target_db_path.exists() {
std::fs::remove_dir_all(&target_db_path)?;
}
std::fs::rename(&export_db_path, &target_db_path)?;
println!(
"{} {} -> {}",
"Renamed export directory:".green(),
source_db,
target_db
);
}
match mongodb::import_database(
target_config,
target_db,
temp_path,
drop_collections,
clear_collections,
)
.await
{
Ok(_) => {
println!("{} {}", "Import completed:".green(), target_db);
}
Err(e) => {
error!("Failed to import database: {}", e);
println!("{} Import failed: {}", "Error:".red().bold(), e);
if let Some(path) = &backup_path {
println!("{} {}", "Restoring backup:".yellow(), path.display());
if let Err(restore_err) =
mongodb::restore_backup(target_config, target_db, path).await
{
error!("Failed to restore backup: {}", restore_err);
println!(
"{} Backup restoration failed: {}",
"Error:".red().bold(),
restore_err
);
} else {
println!("{}", "Backup restored successfully".green());
}
}
}
}
}
Err(e) => {
error!("Failed to export database: {}", e);
println!("{} Export failed: {}", "Error:".red().bold(), e);
}
}
println!("\n{}", "Synchronization completed".green().bold());
Ok(())
}