smbcloud_cli/account/signup/
process.rs

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