smbcloud_cli/account/signup/
process.rs

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