use anyhow::Result; use serde_json::Value; use sqlx::PgPool; use std::collections::HashMap; use uuid::Uuid; use crate::crypto; use crate::service::search::{fetch_entries, fetch_secrets_for_entries}; /// Build an env variable map from entry secrets (for dry-run preview or injection). #[allow(clippy::too_many_arguments)] pub async fn build_env_map( pool: &PgPool, folder: Option<&str>, entry_type: Option<&str>, name: Option<&str>, tags: &[String], only_fields: &[String], prefix: &str, master_key: &[u8; 32], user_id: Option, ) -> Result> { let entries = fetch_entries(pool, folder, entry_type, name, tags, None, None, user_id).await?; if entries.is_empty() { return Ok(HashMap::new()); } let entry_ids: Vec = entries.iter().map(|e| e.id).collect(); let secrets_map = fetch_secrets_for_entries(pool, &entry_ids).await?; let mut combined: HashMap = HashMap::new(); for entry in &entries { let all_fields = secrets_map.get(&entry.id).map(Vec::as_slice).unwrap_or(&[]); let effective_prefix = env_prefix(entry, prefix); let fields: Vec<_> = if only_fields.is_empty() { all_fields.iter().collect() } else { all_fields .iter() .filter(|f| only_fields.contains(&f.name)) .collect() }; for f in fields { let decrypted = crypto::decrypt_json(master_key, &f.encrypted)?; let key = format!( "{}_{}", effective_prefix, f.name.to_uppercase().replace(['-', '.'], "_") ); combined.insert(key, json_to_env_string(&decrypted)); } } Ok(combined) } fn env_prefix(entry: &crate::models::Entry, prefix: &str) -> String { let name_part = entry.name.to_uppercase().replace(['-', '.', ' '], "_"); if prefix.is_empty() { name_part } else { let normalized = prefix.to_uppercase().replace(['-', '.', ' '], "_"); let normalized = normalized.trim_end_matches('_'); format!("{}_{}", normalized, name_part) } } fn json_to_env_string(v: &Value) -> String { match v { Value::String(s) => s.clone(), Value::Null => String::new(), other => other.to_string(), } }