ghtool 0.8.0

A command-line tool for interacting with Github API with some specialized features oriented around Checks
use std::{
    io::{self, Write},
    time::Duration,
};

use eyre::{eyre, Result};
use indicatif::ProgressBar;
use tracing::info;

use crate::{
    github::{AccessToken, AccessTokenResponse, CodeResponse, GithubAuthClient},
    spinner::make_spinner_style,
    term::bold,
    token_store,
};

pub async fn login(hostname: &str) -> Result<()> {
    let auth_client = GithubAuthClient::new()?;
    let code_response = auth_client.get_device_code().await?;

    println!(
        "First copy your one-time code: {}",
        bold(&code_response.user_code)
    );

    prompt_for_user_to_continue("Press Enter to open github.com in your browser...")?;

    info!("Opening {} in browser", code_response.verification_uri);
    open::that(&code_response.verification_uri)?;

    let pb = create_progress_bar();
    let access_token = await_authorization(&auth_client, &code_response).await?;
    pb.finish_and_clear();

    token_store::set_token(hostname, &access_token.access_token)
        .map_err(|e| eyre!("Failed to store token: {}", e))?;

    println!("Logged in to {} account", bold(hostname));
    Ok(())
}

fn prompt_for_user_to_continue(prompt_message: &str) -> io::Result<()> {
    print!("{}", prompt_message);
    io::stdout().flush()?;

    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    Ok(())
}

fn create_progress_bar() -> ProgressBar {
    let pb = ProgressBar::new_spinner();
    pb.enable_steady_tick(Duration::from_millis(100));
    pb.set_style(make_spinner_style());
    pb.set_message("Waiting for authorization...");
    pb
}

async fn await_authorization(
    auth_client: &GithubAuthClient,
    code_response: &CodeResponse,
) -> Result<AccessToken> {
    loop {
        let token_response = auth_client
            .get_access_token(&code_response.device_code)
            .await?;

        match token_response {
            AccessTokenResponse::AuthorizationPending(_) => {
                tokio::time::sleep(Duration::from_secs(code_response.interval.into())).await;
            }
            AccessTokenResponse::AccessToken(token) => {
                return Ok(token);
            }
        };
    }
}