use std::io::{self, Write};
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use crate::atlassian::auth::{self, AtlassianCredentials};
use crate::atlassian::client::AtlassianClient;
#[derive(Parser)]
pub struct AuthCommand {
#[command(subcommand)]
pub command: AuthSubcommands,
}
#[derive(Subcommand)]
pub enum AuthSubcommands {
Login(LoginCommand),
Status(StatusCommand),
}
impl AuthCommand {
pub async fn execute(self) -> Result<()> {
match self.command {
AuthSubcommands::Login(cmd) => cmd.execute(),
AuthSubcommands::Status(cmd) => cmd.execute().await,
}
}
}
#[derive(Parser)]
pub struct LoginCommand;
impl LoginCommand {
pub fn execute(self) -> Result<()> {
println!("Configure Atlassian Cloud credentials\n");
let instance_url = prompt("Instance URL (e.g., https://myorg.atlassian.net): ")?;
if instance_url.is_empty() {
anyhow::bail!("Instance URL is required");
}
let email = prompt("Email: ")?;
if email.is_empty() {
anyhow::bail!("Email is required");
}
let api_token = prompt("API token: ")?;
if api_token.is_empty() {
anyhow::bail!("API token is required");
}
let credentials = AtlassianCredentials {
instance_url: instance_url.clone(),
email: email.clone(),
api_token,
};
auth::save_credentials(&credentials)?;
println!("\nCredentials saved to ~/.omni-dev/settings.json");
println!(" Instance: {instance_url}");
println!(" Email: {email}");
println!("\nRun `omni-dev atlassian auth status` to verify.");
Ok(())
}
}
#[derive(Parser)]
pub struct StatusCommand;
impl StatusCommand {
pub async fn execute(self) -> Result<()> {
let credentials = auth::load_credentials()?;
println!("Checking authentication to {}...", credentials.instance_url);
let client = AtlassianClient::from_credentials(&credentials)?;
let user = client.get_myself().await?;
println!("Authenticated as: {}", user.display_name);
if let Some(ref email) = user.email_address {
println!("Email: {email}");
}
println!("Account ID: {}", user.account_id);
println!("Instance: {}", credentials.instance_url);
Ok(())
}
}
fn prompt(message: &str) -> Result<String> {
print!("{message}");
io::stdout().flush().context("Failed to flush stdout")?;
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.context("Failed to read user input")?;
Ok(input.trim().to_string())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn auth_command_login_dispatch() {
let cmd = AuthCommand {
command: AuthSubcommands::Login(LoginCommand),
};
assert!(matches!(cmd.command, AuthSubcommands::Login(_)));
}
#[test]
fn auth_command_status_dispatch() {
let cmd = AuthCommand {
command: AuthSubcommands::Status(StatusCommand),
};
assert!(matches!(cmd.command, AuthSubcommands::Status(_)));
}
}