tcup 0.1.2

The one and only teacup client!
Documentation
pub mod api;
pub mod cli;
pub mod config;
pub mod db;
pub mod errors;
pub mod git;
pub mod utils;

use futures::stream::{self, StreamExt};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::{fs, process::exit, time::Duration};
use tokio::task;

use bytes::Bytes;

use clap::Parser;
use cli::{Cli, Commands, RepoCommands};

use crate::{db::Repo, errors::TeacupError};

const API_URL: &str = "https://teapot-production.up.railway.app";
// const API_URL: &str = "http://127.0.0.1:8000";

pub async fn run_cli() -> Result<(), TeacupError> {
    // Setup or open configuration
    //
    let config = config::get_config()?;

    // Call setup() on the db - which will create it on the first run
    //
    db::setup(&config)?;

    let cli = Cli::parse();
    match cli.command {
        Commands::Repo { command } => match command {
            RepoCommands::Add { location } => {
                let fullpath = utils::to_absolute_path(&location)?;
                println!("Repo Add: {}", &fullpath);
                if git::is_git_repo(&fullpath) {
                    db::add(&config, &Repo { location: fullpath })?;
                } else {
                    eprintln!("Not a valid local git repository: {}", fullpath);
                }
            }
            RepoCommands::Rm { location } => {
                let fullpath = utils::to_absolute_path(&location)?;
                println!("Repo Rm: {}", &fullpath);
                db::rm(&config, &Repo { location: fullpath })?;
            }
            RepoCommands::List => {
                println!("Repo List");
                let repos = db::list(&config)?;
                println!("{:?}", repos);
            }
            RepoCommands::Search { base_path, add } => {
                println!("Repo Search: {}", base_path);
                let candidates = utils::find_git_repos(&base_path);

                for candidate in candidates {
                    println!("{}", candidate);

                    // Only add if option set
                    //
                    if add {
                        db::add(
                            &config,
                            &Repo {
                                location: candidate,
                            },
                        )?;
                    }
                }
            }
        },
        Commands::Sync { author, alias } => {
            println!("Sync'ing to {}", API_URL);
            let multiprogress = MultiProgress::new();

            let repos = db::list(&config)?;

            // Wrap repos in progress bars
            //
            let repos_with_progress = repos.into_iter().map(move |repo| {
                let pb = multiprogress.add(ProgressBar::new_spinner());
                pb.set_style(
                    ProgressStyle::default_spinner()
                        .template("{spinner:.green} [{elapsed_precise}] {msg}")
                        .unwrap(),
                );
                pb.enable_steady_tick(Duration::from_millis(400));
                pb.set_message(format!("Waiting: {}", repo.location));
                (repo, pb)
            });

            // Process parallel git commit streams
            //
            let commit_stream = stream::iter(repos_with_progress)
                .map(|(repo, pb)| {
                    task::spawn_blocking(move || {
                        pb.set_message(format!("Extracting: {}", repo.location));
                        // println!("Processing {}", &repo.location);
                        match git::extract_logs(repo.location.clone()) {
                            Err(e) => {
                                pb.finish_and_clear();
                                vec![]
                            }
                            Ok(logs) => {
                                pb.finish_and_clear();
                                logs
                            }
                        }
                    })
                })
                .buffer_unordered(num_cpus::get())
                .map(|result| result.unwrap())
                .flat_map(|commits| stream::iter(commits))
                .map(|commit| {
                    let mut json = serde_json::to_vec(&commit)?;
                    json.push(b'\n');
                    Ok::<_, serde_json::Error>(Bytes::from(json))
                });

            let aliases = alias
                .iter()
                .map(|email| format!("alias={}", email))
                .collect::<Vec<String>>()
                .join("&");

            let url = format!("{}/upload?author={}&{}", API_URL, author, aliases);
            println!("Uploading to {}", url);

            let client = reqwest::Client::new();
            let response = client
                .post(url)
                .body(reqwest::Body::wrap_stream(commit_stream))
                .send()
                .await
                .map_err(|e| {
                    dbg!(&e);
                    TeacupError::ApiError(e.to_string())
                })?;

            // Check if the response was successful
            //
            let status = response.status();
            let body = response
                .text()
                .await
                .unwrap_or_else(|_| "Could not read body".to_string());

            eprintln!("Upload complete - Status: {}, Body: {}", status, body);
        }
    }

    Ok(())
}