bular 0.0.2

CLI for managing Bular deployments
use std::{
    collections::HashMap,
    net::SocketAddr,
    sync::{Mutex, PoisonError},
};

use http_body_util::Full;
use hyper::{body::Bytes, server::conn::http1, service::service_fn, Response};
use hyper_util::rt::TokioIo;
use tokio::{net::TcpListener, sync::oneshot};

use crate::bular_json::BularJson;

static BULAR_CLI_CLIENT_ID: &str = "000000000000";

#[derive(clap::Args)]
#[command(about = "Authenticate with Bular Cloud!")]
pub struct Command {}

impl Command {
    pub async fn run(&self, server: String) {
        if let Some(config) = BularJson::load() {
            if config.token.is_some() {
                // TODO: We need to add a logout command that also deletes the token in Bular if it's valid.
                println!("Already authenticated. Please logout first!");
                return;
            }
        }

        let (tx, rx) = oneshot::channel();

        let addr = SocketAddr::from(([127, 0, 0, 1], 2857));
        let listener = TcpListener::bind(addr).await.unwrap();

        tokio::spawn(async move {
            let tx = Mutex::new(Some(tx));
            while tx.lock().unwrap_or_else(PoisonError::into_inner).is_some() {
                let (stream, _) = listener.accept().await.unwrap();
                let io = TokioIo::new(stream);

                // Note: We don't spawn this to it's own task so requests are handled sequentially. This is intentional.
                if let Err(err) = http1::Builder::new()
                    .serve_connection(
                        io,
                        service_fn(|req| {
                            let params: HashMap<String, String> = req
                                .uri()
                                .query()
                                .map(|v| {
                                    url::form_urlencoded::parse(v.as_bytes())
                                        .into_owned()
                                        .collect()
                                })
                                .unwrap_or_else(HashMap::new);

                            let mut ok = false;
                            if let Some(code) = params.get("code") {
                                if let Some(tx) =
                                    tx.lock().unwrap_or_else(PoisonError::into_inner).take()
                                {
                                    tx.send(code.to_string()).unwrap();
                                    ok = true;
                                }
                            }

                            async move {
                                Ok::<_, hyper::Error>(Response::new(Full::new(Bytes::from(
                                    if ok {
                                        "Authenticated successfully! You can close this tab and go back to the application."
                                    } else {
                                        "Error occurred during authentication! Please try again."
                                    },
                                ))))
                            }
                        }),
                    )
                    .await
                {
                    eprintln!("Error serving connection: {:?}", err);
                }
            }
        });

        let url = format!("{server}/auth/cli?client_id={BULAR_CLI_CLIENT_ID}");
        open::that(&url).ok();
        println!("Opened {url:?} in your browser. Follow the instructions to authenticate!");

        let Ok(token) = rx.await else {
            todo!();
        };

        BularJson { token: Some(token) }.save();

        println!("Successfully authenticated!");
    }
}