@smbcloud / cli / commits / 721649e

refactor structure

keypair34 committed Sep 20, 2025 at 20:07 UTC 721649ea04d0213cde8876399bafd1305c9dbf19
11 files changed +285 -221
crates/cli/src/deploy/config.rs
+1 -1
@@ -1,6 +1,6 @@
1 use {
2 crate::{
3 - deploy::setup::setup_project,
3 + deploy::setup_project::setup_project,
4 ui::{fail_message, fail_symbol, succeed_message, succeed_symbol},
5 },
6 git2::{Cred, CredentialType, Error},
crates/cli/src/deploy/mod.rs
+3 -1
@@ -3,4 +3,6 @@ mod detect_runner;
3 mod git;
4 pub mod process_deploy;
5 mod remote_messages;
6 -mod setup;
6 +pub(crate) mod setup_create_new_project;
7 +pub(crate) mod setup_project;
8 +pub(crate) mod setup_select_project;
crates/cli/src/deploy/setup.rs deleted
-210
@@ -1,210 +0,0 @@
1 -use {
2 - crate::token::get_smb_token,
3 - crate::{deploy::config::Config, ui::highlight},
4 - dialoguer::{theme::ColorfulTheme, Confirm, Input, Select},
5 - regex::Regex,
6 - smbcloud_model::{
7 - error_codes::{ErrorCode, ErrorResponse},
8 - project::{Project, ProjectCreate},
9 - },
10 - smbcloud_network::environment::Environment,
11 - smbcloud_networking_project::{
12 - crud_project_create::create_project, crud_project_read::get_projects,
13 - },
14 - std::{env, fs, path::Path},
15 -};
16 -
17 -pub async fn setup_project(env: Environment) -> Result<Config, ErrorResponse> {
18 - let path = env::current_dir().ok();
19 - let path_str = path
20 - .as_ref()
21 - .map(|p| p.display().to_string())
22 - .unwrap_or_else(|| ".".to_string());
23 -
24 - let confirm = Confirm::with_theme(&ColorfulTheme::default())
25 - .with_prompt(format!("Setup project in {}? y/n", highlight(&path_str)))
26 - .interact()
27 - .map_err(|_| ErrorResponse::Error {
28 - error_code: ErrorCode::InputError,
29 - message: ErrorCode::InputError.message(None).to_string(),
30 - })?;
31 -
32 - if !confirm {
33 - return Err(ErrorResponse::Error {
34 - error_code: ErrorCode::Cancel,
35 - message: ErrorCode::Cancel.message(None).to_string(),
36 - });
37 - }
38 -
39 - let access_token = match get_smb_token(env).await {
40 - Ok(token) => token,
41 - Err(_) => {
42 - return Err(ErrorResponse::Error {
43 - error_code: ErrorCode::Unauthorized,
44 - message: ErrorCode::Unauthorized.message(None).to_string(),
45 - })
46 - }
47 - };
48 -
49 - let projects = match get_projects(env, access_token).await {
50 - Ok(x) => x,
51 - Err(_) => {
52 - return Err(ErrorResponse::Error {
53 - error_code: ErrorCode::InputError,
54 - message: ErrorCode::InputError.message(None).to_string(),
55 - })
56 - }
57 - };
58 -
59 - let project: Project = if !projects.is_empty() {
60 - select_project(env, projects, &path_str).await?
61 - } else {
62 - create_new_project(env, &path_str).await?
63 - };
64 -
65 - let name = project.name.clone();
66 - let description = project.description.clone();
67 -
68 - // Create config struct
69 - let config = Config {
70 - project,
71 - name,
72 - description,
73 - };
74 -
75 - // Ensure .smb directory exists
76 - let smb_dir = Path::new(".smb");
77 - if !smb_dir.exists() {
78 - fs::create_dir(smb_dir).map_err(|_| ErrorResponse::Error {
79 - error_code: ErrorCode::MissingConfig,
80 - message: ErrorCode::MissingConfig.message(None).to_string(),
81 - })?;
82 - }
83 -
84 - // Write config to .smb/config.toml
85 - let config_toml = toml::to_string(&config).map_err(|_| ErrorResponse::Error {
86 - error_code: ErrorCode::MissingConfig,
87 - message: ErrorCode::MissingConfig.message(None).to_string(),
88 - })?;
89 - fs::write(".smb/config.toml", config_toml).map_err(|_| ErrorResponse::Error {
90 - error_code: ErrorCode::MissingConfig,
91 - message: ErrorCode::MissingConfig.message(None).to_string(),
92 - })?;
93 -
94 - Ok(config)
95 -}
96 -
97 -async fn select_project(
98 - env: Environment,
99 - projects: Vec<Project>,
100 - path: &str,
101 -) -> Result<Project, ErrorResponse> {
102 - let confirm = Confirm::with_theme(&ColorfulTheme::default())
103 - .with_prompt("Use existing project? y/n")
104 - .interact()
105 - .map_err(|_| ErrorResponse::Error {
106 - error_code: ErrorCode::InputError,
107 - message: ErrorCode::InputError.message(None).to_string(),
108 - })?;
109 -
110 - if !confirm {
111 - return create_new_project(env, path).await;
112 - }
113 - let selection = Select::with_theme(&ColorfulTheme::default())
114 - .items(&projects)
115 - .default(0)
116 - .interact()
117 - .map_err(|_| ErrorResponse::Error {
118 - error_code: ErrorCode::InputError,
119 - message: ErrorCode::InputError.message(None).to_string(),
120 - })?;
121 -
122 - let project = projects[selection].clone();
123 -
124 - Ok(project)
125 -}
126 -
127 -async fn create_new_project(env: Environment, path: &str) -> Result<Project, ErrorResponse> {
128 - let default_name = Path::new(path)
129 - .file_name()
130 - .and_then(|os_str| os_str.to_str())
131 - .unwrap_or("project")
132 - .to_lowercase()
133 - .replace([' ', '-'], "")
134 - .replace('-', "");
135 -
136 - let name = match Input::<String>::with_theme(&ColorfulTheme::default())
137 - .with_prompt("Project name")
138 - .default(default_name.to_string())
139 - .interact()
140 - {
141 - Ok(project_name) => project_name,
142 - Err(_) => {
143 - return Err(ErrorResponse::Error {
144 - error_code: ErrorCode::InputError,
145 - message: ErrorCode::InputError.message(None).to_string(),
146 - });
147 - }
148 - };
149 -
150 - // Create a repository name: lowercased, remove spaces and special characters
151 - let re = Regex::new(r"[^a-zA-Z0-9_-]").unwrap();
152 - let default_repository = name
153 - .clone()
154 - .to_lowercase()
155 - .replace(' ', "_")
156 - .replace('-', "");
157 - let default_repo = re.replace_all(&default_repository, "");
158 -
159 - let repository = match Input::<String>::with_theme(&ColorfulTheme::default())
160 - .default(default_repo.to_string())
161 - .with_prompt("Repository")
162 - .interact()
163 - {
164 - Ok(repo) => repo,
165 - Err(_) => {
166 - return Err(ErrorResponse::Error {
167 - error_code: ErrorCode::InputError,
168 - message: ErrorCode::InputError.message(None).to_string(),
169 - });
170 - }
171 - };
172 -
173 - let description = match Input::<String>::with_theme(&ColorfulTheme::default())
174 - .with_prompt("Description")
175 - .interact()
176 - {
177 - Ok(description) => description,
178 - Err(_) => {
179 - return Err(ErrorResponse::Error {
180 - error_code: ErrorCode::InputError,
181 - message: ErrorCode::InputError.message(None).to_string(),
182 - });
183 - }
184 - };
185 -
186 - let access_token = match get_smb_token(env).await {
187 - Ok(token) => token,
188 - Err(_) => {
189 - return Err(ErrorResponse::Error {
190 - error_code: ErrorCode::Unauthorized,
191 - message: ErrorCode::Unauthorized.message(None).to_string(),
192 - })
193 - }
194 - };
195 -
196 - match create_project(
197 - env,
198 - access_token,
199 - ProjectCreate {
200 - name,
201 - repository,
202 - description,
203 - },
204 - )
205 - .await
206 - {
207 - Ok(project) => Ok(project),
208 - Err(e) => Err(e),
209 - }
210 -}
crates/cli/src/deploy/setup_create_new_project.rs new
+100
@@ -0,0 +1,100 @@
1 +use {
2 + crate::token::get_smb_token,
3 + dialoguer::{theme::ColorfulTheme, Input},
4 + regex::Regex,
5 + smbcloud_model::{
6 + error_codes::{ErrorCode, ErrorResponse},
7 + project::{Project, ProjectCreate},
8 + },
9 + smbcloud_network::environment::Environment,
10 + smbcloud_networking_project::crud_project_create::create_project,
11 + std::path::Path,
12 +};
13 +
14 +pub(crate) async fn create_new_project(
15 + env: Environment,
16 + path: &str,
17 +) -> Result<Project, ErrorResponse> {
18 + let default_name = Path::new(path)
19 + .file_name()
20 + .and_then(|os_str| os_str.to_str())
21 + .unwrap_or("project")
22 + .to_lowercase()
23 + .replace([' ', '-'], "")
24 + .replace('-', "");
25 +
26 + let name = match Input::<String>::with_theme(&ColorfulTheme::default())
27 + .with_prompt("Project name")
28 + .default(default_name.to_string())
29 + .interact()
30 + {
31 + Ok(project_name) => project_name,
32 + Err(_) => {
33 + return Err(ErrorResponse::Error {
34 + error_code: ErrorCode::InputError,
35 + message: ErrorCode::InputError.message(None).to_string(),
36 + });
37 + }
38 + };
39 +
40 + // Create a repository name: lowercased, remove spaces and special characters
41 + let re = Regex::new(r"[^a-zA-Z0-9_-]").unwrap();
42 + let default_repository = name
43 + .clone()
44 + .to_lowercase()
45 + .replace(' ', "_")
46 + .replace('-', "");
47 + let default_repo = re.replace_all(&default_repository, "");
48 +
49 + let repository = match Input::<String>::with_theme(&ColorfulTheme::default())
50 + .default(default_repo.to_string())
51 + .with_prompt("Repository")
52 + .interact()
53 + {
54 + Ok(repo) => repo,
55 + Err(_) => {
56 + return Err(ErrorResponse::Error {
57 + error_code: ErrorCode::InputError,
58 + message: ErrorCode::InputError.message(None).to_string(),
59 + });
60 + }
61 + };
62 +
63 + let description = match Input::<String>::with_theme(&ColorfulTheme::default())
64 + .with_prompt("Description")
65 + .interact()
66 + {
67 + Ok(description) => description,
68 + Err(_) => {
69 + return Err(ErrorResponse::Error {
70 + error_code: ErrorCode::InputError,
71 + message: ErrorCode::InputError.message(None).to_string(),
72 + });
73 + }
74 + };
75 +
76 + let access_token = match get_smb_token(env).await {
77 + Ok(token) => token,
78 + Err(_) => {
79 + return Err(ErrorResponse::Error {
80 + error_code: ErrorCode::Unauthorized,
81 + message: ErrorCode::Unauthorized.message(None).to_string(),
82 + })
83 + }
84 + };
85 +
86 + match create_project(
87 + env,
88 + access_token,
89 + ProjectCreate {
90 + name,
91 + repository,
92 + description,
93 + },
94 + )
95 + .await
96 + {
97 + Ok(project) => Ok(project),
98 + Err(e) => Err(e),
99 + }
100 +}
crates/cli/src/deploy/setup_project.rs new
+90
@@ -0,0 +1,90 @@
1 +use {
2 + crate::{
3 + deploy::{
4 + config::Config, setup_create_new_project::create_new_project,
5 + setup_select_project::select_project,
6 + },
7 + token::get_smb_token,
8 + ui::highlight,
9 + },
10 + dialoguer::{theme::ColorfulTheme, Confirm},
11 + smbcloud_model::{
12 + error_codes::{ErrorCode, ErrorResponse},
13 + project::Project,
14 + },
15 + smbcloud_network::environment::Environment,
16 + smbcloud_networking_project::crud_project_read::get_projects,
17 + std::{env, fs, path::Path},
18 +};
19 +
20 +pub(crate) async fn setup_project(env: Environment) -> Result<Config, ErrorResponse> {
21 + let path = env::current_dir().ok();
22 + let path_str = path
23 + .as_ref()
24 + .map(|p| p.display().to_string())
25 + .unwrap_or_else(|| ".".to_string());
26 +
27 + let confirm = Confirm::with_theme(&ColorfulTheme::default())
28 + .with_prompt(format!("Setup project in {}? y/n", highlight(&path_str)))
29 + .interact()
30 + .map_err(|_| ErrorResponse::Error {
31 + error_code: ErrorCode::InputError,
32 + message: ErrorCode::InputError.message(None).to_string(),
33 + })?;
34 +
35 + if !confirm {
36 + return Err(ErrorResponse::Error {
37 + error_code: ErrorCode::Cancel,
38 + message: ErrorCode::Cancel.message(None).to_string(),
39 + });
40 + }
41 +
42 + let access_token = match get_smb_token(env).await {
43 + Ok(token) => token,
44 + Err(_) => {
45 + return Err(ErrorResponse::Error {
46 + error_code: ErrorCode::Unauthorized,
47 + message: ErrorCode::Unauthorized.message(None).to_string(),
48 + })
49 + }
50 + };
51 +
52 + let projects = get_projects(env, access_token).await?;
53 +
54 + let project: Project = if !projects.is_empty() {
55 + select_project(env, projects, &path_str).await?
56 + } else {
57 + create_new_project(env, &path_str).await?
58 + };
59 +
60 + let name = project.name.clone();
61 + let description = project.description.clone();
62 +
63 + // Create config struct
64 + let config = Config {
65 + project,
66 + name,
67 + description,
68 + };
69 +
70 + // Ensure .smb directory exists
71 + let smb_dir = Path::new(".smb");
72 + if !smb_dir.exists() {
73 + fs::create_dir(smb_dir).map_err(|_| ErrorResponse::Error {
74 + error_code: ErrorCode::MissingConfig,
75 + message: ErrorCode::MissingConfig.message(None).to_string(),
76 + })?;
77 + }
78 +
79 + // Write config to .smb/config.toml
80 + let config_toml = toml::to_string(&config).map_err(|_| ErrorResponse::Error {
81 + error_code: ErrorCode::MissingConfig,
82 + message: ErrorCode::MissingConfig.message(None).to_string(),
83 + })?;
84 + fs::write(".smb/config.toml", config_toml).map_err(|_| ErrorResponse::Error {
85 + error_code: ErrorCode::MissingConfig,
86 + message: ErrorCode::MissingConfig.message(None).to_string(),
87 + })?;
88 +
89 + Ok(config)
90 +}
crates/cli/src/deploy/setup_select_project.rs new
+39
@@ -0,0 +1,39 @@
1 +use {
2 + crate::deploy::setup_create_new_project::create_new_project,
3 + dialoguer::{theme::ColorfulTheme, Confirm, Select},
4 + smbcloud_model::{
5 + error_codes::{ErrorCode, ErrorResponse},
6 + project::Project,
7 + },
8 + smbcloud_network::environment::Environment,
9 +};
10 +
11 +pub(crate) async fn select_project(
12 + env: Environment,
13 + projects: Vec<Project>,
14 + path: &str,
15 +) -> Result<Project, ErrorResponse> {
16 + let confirm = Confirm::with_theme(&ColorfulTheme::default())
17 + .with_prompt("Use existing project? y/n")
18 + .interact()
19 + .map_err(|_| ErrorResponse::Error {
20 + error_code: ErrorCode::InputError,
21 + message: ErrorCode::InputError.message(None).to_string(),
22 + })?;
23 +
24 + if !confirm {
25 + return create_new_project(env, path).await;
26 + }
27 + let selection = Select::with_theme(&ColorfulTheme::default())
28 + .items(&projects)
29 + .default(0)
30 + .interact()
31 + .map_err(|_| ErrorResponse::Error {
32 + error_code: ErrorCode::InputError,
33 + message: ErrorCode::InputError.message(None).to_string(),
34 + })?;
35 +
36 + let project = projects[selection].clone();
37 +
38 + Ok(project)
39 +}
crates/cli/src/token/mod.rs
+20 -5
@@ -1,8 +1,12 @@
1 -use anyhow::{anyhow, Result};
2 -use dirs::home_dir;
3 -use log::debug;
4 -use smbcloud_network::environment::Environment;
5 -use std::path::{Path, PathBuf};
1 +use {
2 + anyhow::{anyhow, Result},
3 + dirs::home_dir,
4 + log::debug,
5 + smbcloud_network::environment::Environment,
6 + std::path::{Path, PathBuf},
7 +};
8 +
9 +pub(crate) mod test_token_validity;
10
11 pub async fn get_smb_token(env: Environment) -> Result<String> {
12 if let Some(path) = smb_token_file_path(env) {
@@ -31,3 +35,14 @@ pub fn smb_token_file_path(env: Environment) -> Option<PathBuf> {
35 }
36 }
37 }
38 +
39 +pub fn remove_smb_token(env: Environment) -> Result<()> {
40 + if let Some(path) = smb_token_file_path(env) {
41 + std::fs::remove_file(path).map_err(|e| {
42 + debug!("Error while removing token: {}", &e);
43 + anyhow!("Error while removing token. Are you logged in?")
44 + })
45 + } else {
46 + Err(anyhow!("Failed to get home directory. Are you logged in?"))
47 + }
48 +}
crates/cli/src/token/test_token_validity.rs new
+25
@@ -0,0 +1,25 @@
1 +use crate::token::{get_smb_token, smb_token_file_path};
2 +use anyhow::{anyhow, Result};
3 +use dialoguer::{theme::ColorfulTheme, Confirm};
4 +use reqwest::{Client, StatusCode};
5 +use smbcloud_network::environment::Environment;
6 +use smbcloud_networking::{constants::PATH_USERS_SIGN_OUT, smb_base_url_builder};
7 +use spinners::Spinner;
8 +use std::fs;
9 +
10 +async fn do_process_logout(env: Environment) -> Result<()> {
11 + let token = get_smb_token(env).await?;
12 +
13 + let response = Client::new()
14 + .delete(build_smb_logout_url(env))
15 + .header("Authorization", token)
16 + .header("Accept", "application/json")
17 + .header("Content-Type", "application/x-www-form-urlencoded")
18 + .send()
19 + .await?;
20 +
21 + match response.status() {
22 + StatusCode::OK => Ok(()),
23 + _ => Err(anyhow!("Failed to logout.")),
24 + }
25 +}
crates/smbcloud-network/src/network.rs
+1 -1
@@ -4,7 +4,7 @@ use serde::de::DeserializeOwned;
4 use smbcloud_model::error_codes::{ErrorCode, ErrorResponse};
5 //use std::time::Duration;
6 #[cfg(debug_assertions)]
7 -const LOG_RESPONSE_BODY: bool = false; // You know what to do here.
7 +const LOG_RESPONSE_BODY: bool = true; // You know what to do here.
8 #[cfg(not(debug_assertions))]
9 const LOG_RESPONSE_BODY: bool = false;
10
crates/smbcloud-networking/src/lib.rs
+6 -3
@@ -1,8 +1,11 @@
1 pub mod constants;
2 +pub mod valid_token;
3
3 -use constants::{SMB_CLIENT_ID, SMB_CLIENT_SECRET};
4 -use smbcloud_network::environment::Environment;
5 -use url_builder::URLBuilder;
4 +use {
5 + constants::{SMB_CLIENT_ID, SMB_CLIENT_SECRET},
6 + smbcloud_network::environment::Environment,
7 + url_builder::URLBuilder,
8 +};
9
10 pub fn smb_base_url_builder(env: Environment) -> URLBuilder {
11 let mut url_builder = URLBuilder::new();
crates/smbcloud-networking/src/valid_token.rs