smbcloud_cli/account/signup/
process.rs

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