use anyhow::{anyhow, Result};
use serde::Deserialize;
use serde_json::json;
use crate::client::{AppStoreConnectClient, AscResponse, BASE_URL};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct BetaTesterData {
id: String,
#[allow(dead_code)]
#[serde(rename = "type")]
data_type: String,
}
impl AppStoreConnectClient {
pub async fn add_beta_tester(
&self,
email: &str,
first_name: &str,
last_name: &str,
beta_group_id: Option<&str>,
) -> Result<String> {
let token = self.generate_token()?;
let group_id = beta_group_id
.map(|s| s.to_string())
.or_else(|| self.default_beta_group_id.clone())
.ok_or_else(|| anyhow!("No beta group ID provided and ASC_BETA_GROUP_ID not set"))?;
let body = json!({
"data": {
"type": "betaTesters",
"attributes": {
"email": email,
"firstName": first_name,
"lastName": last_name
},
"relationships": {
"betaGroups": {
"data": [
{
"type": "betaGroups",
"id": group_id
}
]
}
}
}
});
let response = self.client
.post(format!("{}/betaTesters", BASE_URL))
.header("Authorization", format!("Bearer {}", token))
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|e| anyhow!("Failed to call App Store Connect API: {}", e))?;
let status = response.status();
if status.is_success() {
let result: AscResponse<BetaTesterData> = response.json().await
.map_err(|e| anyhow!("Failed to parse ASC response: {}", e))?;
log::info!("Added beta tester {} (id: {})", email, result.data.id);
Ok(result.data.id)
} else if status.as_u16() == 409 {
log::info!("Beta tester {} already exists, skipping", email);
Ok(String::from("already_exists"))
} else {
let error_body = response.text().await.unwrap_or_default();
Err(anyhow!("App Store Connect API error ({}): {}", status, Self::parse_error(status, &error_body)))
}
}
pub async fn remove_tester_from_group(
&self,
tester_id: &str,
beta_group_id: Option<&str>,
) -> Result<()> {
let token = self.generate_token()?;
let group_id = beta_group_id
.map(|s| s.to_string())
.or_else(|| self.default_beta_group_id.clone())
.ok_or_else(|| anyhow!("No beta group ID provided and ASC_BETA_GROUP_ID not set"))?;
let body = json!({
"data": [
{
"type": "betaTesters",
"id": tester_id
}
]
});
let response = self.client
.delete(format!("{}/betaGroups/{}/relationships/betaTesters", BASE_URL, group_id))
.header("Authorization", format!("Bearer {}", token))
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|e| anyhow!("Failed to call App Store Connect API: {}", e))?;
if response.status().is_success() || response.status().as_u16() == 204 {
log::info!("Removed tester {} from group {}", tester_id, group_id);
Ok(())
} else {
let error_body = response.text().await.unwrap_or_default();
Err(anyhow!("Failed to remove tester from group: {}", error_body))
}
}
}