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    signup_input_password_step(env, email).await
57}
58
59async fn signup_input_password_step(env: Environment, email: String) -> Result<CommandResult> {
60    let password = Password::with_theme(&ColorfulTheme::default())
61        .with_prompt("Password")
62        .validate_with(|input: &String| -> Result<(), &str> {
63            if input.len() >= 6 {
64                Ok(())
65            } else {
66                Err("Password must be at least 6 characters")
67            }
68        })
69        .with_confirmation("Confirm password", "Passwords do not match")
70        .interact()
71        .unwrap();
72
73    let spinner = Spinner::new(
74        spinners::Spinners::BouncingBall,
75        succeed_message("Signing up"),
76    );
77
78    let params = SignupEmailParams {
79        user: SignupUserEmail { email, password },
80    };
81
82    match do_signup(env, &params).await {
83        Ok(_) => Ok(CommandResult {
84            spinner,
85            symbol: succeed_symbol(),
86            msg: succeed_message("You are signed up! Check your email to confirm your account."),
87        }),
88        Err(e) => Ok(CommandResult {
89            spinner,
90            symbol: fail_symbol(),
91            msg: fail_message(&format!("{e}")),
92        }),
93    }
94}
95
96async fn signup_with_github(env: Environment) -> Result<CommandResult> {
97    match authorize_github(&env).await {
98        Ok(code) => {
99            debug!("Code: {:#?}", code);
100            Ok(CommandResult {
101                spinner: Spinner::new(
102                    spinners::Spinners::BouncingBall,
103                    succeed_message("Requesting GitHub token"),
104                ),
105                symbol: succeed_symbol(),
106                msg: succeed_message("Finished requesting GitHub token!"),
107            })
108        }
109        Err(e) => {
110            let error = anyhow!("Failed to get code from channel: {e}");
111            Err(error)
112        }
113    }
114}
115
116pub async fn do_signup<T: Serialize + ?Sized>(env: Environment, args: &T) -> Result<CommandResult> {
117    let spinner = Spinner::new(
118        spinners::Spinners::BouncingBall,
119        succeed_message("Signing you up"),
120    );
121
122    let response = Client::new()
123        .post(build_smb_signup_url(env))
124        .json(&args)
125        .send()
126        .await?;
127
128    match response.status() {
129        StatusCode::OK => Ok(CommandResult {
130            spinner,
131            symbol: succeed_symbol(),
132            msg: succeed_message(
133                "Your account has been created. Check email for verification link.",
134            ),
135        }),
136        StatusCode::UNPROCESSABLE_ENTITY => {
137            let result: SignupResult = response.json().await?;
138            let error = anyhow!("Failed to signup: {}", result.status.message);
139            Err(error)
140        }
141        _ => {
142            let error = anyhow!("Failed to signup: {}", response.status());
143            Err(error)
144        }
145    }
146}
147
148fn build_smb_signup_url(env: Environment) -> String {
149    let mut url_builder = smb_base_url_builder(env);
150    url_builder.add_route(PATH_USERS);
151    url_builder.build()
152}