use actix_web::{web, HttpResponse};
use crate::{
app_state::{AppState, ConfigUpdateEffects},
error::AppError,
};
use bamboo_infrastructure::config::EnvVarEntry;
use super::{
types::{EnvVarResponse, EnvVarsListResponse, ReplaceEnvVarsRequest, UpsertEnvVarRequest},
validation::{check_duplicate_names, validate_env_var_name, validate_env_var_value},
};
pub async fn list_env_vars(app_state: web::Data<AppState>) -> Result<HttpResponse, AppError> {
let config = app_state.config.read().await;
let entries: Vec<EnvVarResponse> = config
.env_vars
.iter()
.map(EnvVarResponse::from_entry)
.collect();
Ok(HttpResponse::Ok().json(EnvVarsListResponse { entries }))
}
pub async fn upsert_env_var(
app_state: web::Data<AppState>,
payload: web::Json<UpsertEnvVarRequest>,
) -> Result<HttpResponse, AppError> {
let req = payload.into_inner();
validate_env_var_name(&req.name)?;
validate_env_var_value(&req.value)?;
let updated = app_state
.update_config(
|cfg| {
if let Some(existing) = cfg.env_vars.iter_mut().find(|e| e.name == req.name) {
if !req.value.trim().is_empty() {
existing.value = req.value.clone();
}
existing.secret = req.secret;
existing.value_encrypted = None; existing.description = req.description.clone();
} else {
cfg.env_vars.push(EnvVarEntry {
name: req.name.clone(),
value: req.value.clone(),
secret: req.secret,
value_encrypted: None,
description: req.description.clone(),
});
}
Ok(())
},
ConfigUpdateEffects::default(),
)
.await?;
let entries: Vec<EnvVarResponse> = updated
.env_vars
.iter()
.map(EnvVarResponse::from_entry)
.collect();
Ok(HttpResponse::Ok().json(EnvVarsListResponse { entries }))
}
pub async fn replace_env_vars(
app_state: web::Data<AppState>,
payload: web::Json<ReplaceEnvVarsRequest>,
) -> Result<HttpResponse, AppError> {
let req = payload.into_inner();
let names: Vec<&str> = req.entries.iter().map(|e| e.name.as_str()).collect();
check_duplicate_names(&names)?;
for entry in &req.entries {
validate_env_var_name(&entry.name)?;
validate_env_var_value(&entry.value)?;
}
let new_entries: Vec<EnvVarEntry> = req
.entries
.into_iter()
.map(|e| EnvVarEntry {
name: e.name,
value: e.value,
secret: e.secret,
value_encrypted: None,
description: e.description,
})
.collect();
let updated = app_state
.update_config(
|cfg| {
cfg.env_vars = new_entries.clone();
Ok(())
},
ConfigUpdateEffects::default(),
)
.await?;
let entries: Vec<EnvVarResponse> = updated
.env_vars
.iter()
.map(EnvVarResponse::from_entry)
.collect();
Ok(HttpResponse::Ok().json(EnvVarsListResponse { entries }))
}
pub async fn delete_env_var(
app_state: web::Data<AppState>,
path: web::Path<String>,
) -> Result<HttpResponse, AppError> {
let name = path.into_inner();
let updated = app_state
.update_config(
|cfg| {
let before = cfg.env_vars.len();
cfg.env_vars.retain(|e| e.name != name);
if cfg.env_vars.len() == before {
return Err(AppError::NotFound(format!(
"Environment variable '{}' not found",
name
)));
}
Ok(())
},
ConfigUpdateEffects::default(),
)
.await?;
let entries: Vec<EnvVarResponse> = updated
.env_vars
.iter()
.map(EnvVarResponse::from_entry)
.collect();
Ok(HttpResponse::Ok().json(EnvVarsListResponse { entries }))
}