smbcloud_cli/account/signup/
process.rs

1use super::SignupMethod;
2use crate::{account::lib::authorize_github, cli::CommandResult};
3use anyhow::{anyhow, Result};
4use console::{style, Term};
5use dialoguer::{theme::ColorfulTheme, Input, Password, Select};
6use log::debug;
7use reqwest::{Client, StatusCode};
8use serde::Serialize;
9use smbcloud_model::signup::{SignupEmailParams, SignupResult, SignupUserEmail};
10use smbcloud_networking::{constants::PATH_USERS, smb_base_url_builder, smb_token_file_path};
11use smbcloud_utils::email_validation;
12use spinners::Spinner;
13
14pub async fn process_signup() -> Result<CommandResult> {
15    // Check if token file exists
16    if smb_token_file_path().is_some() {
17        return Ok(CommandResult {
18            spinner: Spinner::new(
19                spinners::Spinners::SimpleDotsScrolling,
20                style("Loading...").green().bold().to_string(),
21            ),
22            symbol: "✅".to_owned(),
23            msg: "You are already logged in. Please logout first.".to_owned(),
24        });
25    }
26
27    let signup_methods = vec![SignupMethod::Email, SignupMethod::GitHub];
28    let selection = Select::with_theme(&ColorfulTheme::default())
29        .items(&signup_methods)
30        .default(0)
31        .interact_on_opt(&Term::stderr())
32        .map(|i| signup_methods[i.unwrap()])
33        .unwrap();
34
35    match selection {
36        SignupMethod::Email => signup_with_email(None).await,
37        SignupMethod::GitHub => signup_with_github().await,
38    }
39}
40
41pub async fn signup_with_email(email: Option<String>) -> Result<CommandResult> {
42    let email = if let Some(email) = email {
43        email
44    } else {
45        Input::<String>::with_theme(&ColorfulTheme::default())
46            .with_prompt("Username")
47            .validate_with(|email: &String| email_validation(email))
48            .interact()
49            .unwrap()
50    };
51
52    let password = Password::with_theme(&ColorfulTheme::default())
53        .with_prompt("Password")
54        .validate_with(|input: &String| -> Result<(), &str> {
55            if input.len() >= 6 {
56                Ok(())
57            } else {
58                Err("Password must be at least 6 characters")
59            }
60        })
61        .with_confirmation("Confirm password", "Passwords do not match")
62        .interact()
63        .unwrap();
64
65    let spinner = Spinner::new(
66        spinners::Spinners::BouncingBall,
67        style("Signing up...").green().bold().to_string(),
68    );
69
70    let params = SignupEmailParams {
71        user: SignupUserEmail { email, password },
72    };
73
74    match do_signup(&params).await {
75        Ok(_) => Ok(CommandResult {
76            spinner,
77            symbol: style("✅".to_string()).for_stderr().green().to_string(),
78            msg: "You are signed up! Check your email to confirm your account.".to_owned(),
79        }),
80        Err(e) => Ok(CommandResult {
81            spinner,
82            symbol: style("✘".to_string()).for_stderr().red().to_string(),
83            msg: format!("{e}"),
84        }),
85    }
86}
87
88async fn signup_with_github() -> Result<CommandResult> {
89    match authorize_github().await {
90        Ok(code) => {
91            debug!("Code: {:#?}", code);
92            Ok(CommandResult {
93                spinner: Spinner::new(
94                    spinners::Spinners::BouncingBall,
95                    style("Requesting GitHub token...")
96                        .green()
97                        .bold()
98                        .to_string(),
99                ),
100                symbol: style("✅".to_string()).for_stderr().green().to_string(),
101                msg: "Finished requesting GitHub token!".to_owned(),
102            })
103        }
104        Err(e) => {
105            let error = anyhow!("Failed to get code from channel: {e}");
106            Err(error)
107        }
108    }
109}
110
111pub async fn do_signup<T: Serialize + ?Sized>(args: &T) -> Result<CommandResult> {
112    let spinner = Spinner::new(
113        spinners::Spinners::BouncingBall,
114        style("Signing you up...").green().bold().to_string(),
115    );
116
117    let response = Client::new()
118        .post(build_smb_signup_url())
119        .json(&args)
120        .send()
121        .await?;
122
123    match response.status() {
124        StatusCode::OK => Ok(CommandResult {
125            spinner,
126            symbol: "✅".to_owned(),
127            msg: "Your account has been created. Check email for verification link.".to_owned(),
128        }),
129        StatusCode::UNPROCESSABLE_ENTITY => {
130            let result: SignupResult = response.json().await?;
131            let error = anyhow!("Failed to signup: {}", result.status.message);
132            Err(error)
133        }
134        _ => {
135            let error = anyhow!("Failed to signup: {}", response.status());
136            Err(error)
137        }
138    }
139}
140
141fn build_smb_signup_url() -> String {
142    let mut url_builder = smb_base_url_builder();
143    url_builder.add_route(PATH_USERS);
144    url_builder.build()
145}