Skip to content

Commit ed2f6b9

Browse files
committed
Merge branch 'feature/project-selection-cancellation' into development
2 parents fad4575 + 90cee56 commit ed2f6b9

File tree

6 files changed

+264
-18
lines changed

6 files changed

+264
-18
lines changed

crates/cli/src/deploy/config.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ use {
1616
std::{fs, path::Path},
1717
};
1818

19-
pub(crate) async fn check_config(env: Environment) -> Result<Config, ErrorResponse> {
19+
pub(crate) async fn check_config(
20+
env: Environment,
21+
access_token: Option<&str>,
22+
) -> Result<Config, ErrorResponse> {
2023
let mut spinner: Spinner = Spinner::new(
2124
spinners::Spinners::SimpleDotsScrolling,
2225
succeed_message("Checking config"),
@@ -28,7 +31,8 @@ pub(crate) async fn check_config(env: Environment) -> Result<Config, ErrorRespon
2831
let config_path = Path::new(".smb/config.toml");
2932
if !config_path.exists() {
3033
spinner.stop_and_persist(&succeed_symbol(), succeed_message("Setting up deployment"));
31-
setup_project(env).await?;
34+
// Let's guide the user through the setup process
35+
setup_project(env, access_token).await?;
3236
spinner = Spinner::new(
3337
spinners::Spinners::SimpleDotsScrolling,
3438
succeed_message("Checking config"),

crates/cli/src/deploy/process_deploy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
3838
let access_token = get_smb_token(env)?;
3939

4040
// Check config.
41-
let config = check_config(env).await?;
41+
let config = check_config(env, Some(&access_token)).await?;
4242

4343
// Check runner.
4444
let runner = detect_runner().await?;

crates/cli/src/deploy/setup.rs

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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(
18+
env: Environment,
19+
access_token: Option<&str>,
20+
) -> 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 access_token {
43+
Some(token) => token.to_string(),
44+
None => {
45+
let access_token = match get_smb_token(env).await {
46+
Ok(token) => token,
47+
Err(_) => {
48+
return Err(ErrorResponse::Error {
49+
error_code: ErrorCode::Unauthorized,
50+
message: ErrorCode::Unauthorized.message(None).to_string(),
51+
})
52+
}
53+
};
54+
access_token
55+
}
56+
};
57+
58+
let projects = match get_projects(env, access_token).await {
59+
Ok(x) => x,
60+
Err(_) => {
61+
return Err(ErrorResponse::Error {
62+
error_code: ErrorCode::InputError,
63+
message: ErrorCode::InputError.message(None).to_string(),
64+
})
65+
}
66+
};
67+
68+
let project: Project = if !projects.is_empty() {
69+
select_project(env, projects, &path_str).await?
70+
} else {
71+
create_new_project(env, &path_str).await?
72+
};
73+
74+
let name = project.name.clone();
75+
let description = project.description.clone();
76+
77+
// Create config struct
78+
let config = Config {
79+
project,
80+
name,
81+
description,
82+
};
83+
84+
// Ensure .smb directory exists
85+
let smb_dir = Path::new(".smb");
86+
if !smb_dir.exists() {
87+
fs::create_dir(smb_dir).map_err(|_| ErrorResponse::Error {
88+
error_code: ErrorCode::MissingConfig,
89+
message: ErrorCode::MissingConfig.message(None).to_string(),
90+
})?;
91+
}
92+
93+
// Write config to .smb/config.toml
94+
let config_toml = toml::to_string(&config).map_err(|_| ErrorResponse::Error {
95+
error_code: ErrorCode::MissingConfig,
96+
message: ErrorCode::MissingConfig.message(None).to_string(),
97+
})?;
98+
fs::write(".smb/config.toml", config_toml).map_err(|_| ErrorResponse::Error {
99+
error_code: ErrorCode::MissingConfig,
100+
message: ErrorCode::MissingConfig.message(None).to_string(),
101+
})?;
102+
103+
Ok(config)
104+
}
105+
106+
async fn select_project(
107+
env: Environment,
108+
projects: Vec<Project>,
109+
path: &str,
110+
) -> Result<Project, ErrorResponse> {
111+
let confirm = Confirm::with_theme(&ColorfulTheme::default())
112+
.with_prompt("Use existing project? y/n")
113+
.interact()
114+
.map_err(|_| ErrorResponse::Error {
115+
error_code: ErrorCode::InputError,
116+
message: ErrorCode::InputError.message(None).to_string(),
117+
})?;
118+
119+
if !confirm {
120+
return create_new_project(env, path).await;
121+
}
122+
let selection = match Select::with_theme(&ColorfulTheme::default())
123+
.with_prompt("Use space/enter to select. Press q or esc to create a new project.")
124+
.items(&projects)
125+
.default(0)
126+
.interact_opt()
127+
.map_err(|_| ErrorResponse::Error {
128+
error_code: ErrorCode::InputError,
129+
message: ErrorCode::InputError.message(None).to_string(),
130+
}) {
131+
Ok(selection) => match selection {
132+
Some(selection) => selection,
133+
None => {
134+
return create_new_project(env, path).await;
135+
}
136+
},
137+
_ => {
138+
return Err(ErrorResponse::Error {
139+
error_code: ErrorCode::InputError,
140+
message: ErrorCode::InputError.message(None).to_string(),
141+
})
142+
}
143+
};
144+
145+
let project = projects[selection].clone();
146+
147+
Ok(project)
148+
}
149+
150+
async fn create_new_project(env: Environment, path: &str) -> Result<Project, ErrorResponse> {
151+
let default_name = Path::new(path)
152+
.file_name()
153+
.and_then(|os_str| os_str.to_str())
154+
.unwrap_or("project")
155+
.to_lowercase()
156+
.replace([' ', '-'], "")
157+
.replace('-', "");
158+
159+
let name = match Input::<String>::with_theme(&ColorfulTheme::default())
160+
.with_prompt("Project name")
161+
.default(default_name.to_string())
162+
.interact()
163+
{
164+
Ok(project_name) => project_name,
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+
// Create a repository name: lowercased, remove spaces and special characters
174+
let re = Regex::new(r"[^a-zA-Z0-9_-]").unwrap();
175+
let default_repository = name
176+
.clone()
177+
.to_lowercase()
178+
.replace(' ', "_")
179+
.replace('-', "");
180+
let default_repo = re.replace_all(&default_repository, "");
181+
182+
let repository = match Input::<String>::with_theme(&ColorfulTheme::default())
183+
.default(default_repo.to_string())
184+
.with_prompt("Repository")
185+
.interact()
186+
{
187+
Ok(repo) => repo,
188+
Err(_) => {
189+
return Err(ErrorResponse::Error {
190+
error_code: ErrorCode::InputError,
191+
message: ErrorCode::InputError.message(None).to_string(),
192+
});
193+
}
194+
};
195+
196+
let description = match Input::<String>::with_theme(&ColorfulTheme::default())
197+
.with_prompt("Description")
198+
.interact()
199+
{
200+
Ok(description) => description,
201+
Err(_) => {
202+
return Err(ErrorResponse::Error {
203+
error_code: ErrorCode::InputError,
204+
message: ErrorCode::InputError.message(None).to_string(),
205+
});
206+
}
207+
};
208+
209+
let access_token = match get_smb_token(env).await {
210+
Ok(token) => token,
211+
Err(_) => {
212+
return Err(ErrorResponse::Error {
213+
error_code: ErrorCode::Unauthorized,
214+
message: ErrorCode::Unauthorized.message(None).to_string(),
215+
})
216+
}
217+
};
218+
219+
match create_project(
220+
env,
221+
access_token,
222+
ProjectCreate {
223+
name,
224+
repository,
225+
description,
226+
},
227+
)
228+
.await
229+
{
230+
Ok(project) => Ok(project),
231+
Err(e) => Err(e),
232+
}
233+
}

crates/cli/src/deploy/setup_project.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ use {
1717
std::{env, fs, path::Path},
1818
};
1919

20-
pub(crate) async fn setup_project(env: Environment) -> Result<Config, ErrorResponse> {
20+
pub(crate) async fn setup_project(
21+
env: Environment,
22+
access_token: Option<&str>,
23+
) -> Result<Config, ErrorResponse> {
2124
let path = env::current_dir().ok();
2225
let path_str = path
2326
.as_ref()
@@ -39,17 +42,20 @@ pub(crate) async fn setup_project(env: Environment) -> Result<Config, ErrorRespo
3942
});
4043
}
4144

42-
let access_token = match get_smb_token(env) {
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-
}
45+
let access_token = match access_token {
46+
Some(token) => token.to_string(),
47+
None => match get_smb_token(env) {
48+
Ok(token) => token,
49+
Err(_) => {
50+
return Err(ErrorResponse::Error {
51+
error_code: ErrorCode::Unauthorized,
52+
message: ErrorCode::Unauthorized.message(None).to_string(),
53+
})
54+
}
55+
},
5056
};
5157

52-
let projects = get_projects(env, access_token).await?;
58+
let projects = get_projects(env, access_token.to_string()).await?;
5359

5460
let project: Project = if !projects.is_empty() {
5561
select_project(env, projects, &path_str).await?

crates/cli/src/project/deployment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub(crate) async fn process_deployment(
1818
let mut spinner: Spinner =
1919
Spinner::new(spinners::Spinners::Hamburger, succeed_message("Loading"));
2020
// Load project id from .smb/config.toml
21-
let config = check_config(env).await?;
21+
let config = check_config(env, None).await?;
2222

2323
let access_token = get_smb_token(env)?;
2424

crates/smbcloud-network/src/network.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
use log::{debug, error};
2-
use reqwest::{RequestBuilder, Response};
3-
use serde::de::DeserializeOwned;
4-
use smbcloud_model::error_codes::{ErrorCode, ErrorResponse};
1+
use {
2+
log::{debug, error},
3+
reqwest::{RequestBuilder, Response},
4+
serde::de::DeserializeOwned,
5+
smbcloud_model::error_codes::{ErrorCode, ErrorResponse},
6+
};
7+
58
//use std::time::Duration;
69
#[cfg(debug_assertions)]
710
const LOG_RESPONSE_BODY: bool = true; // You know what to do here.

0 commit comments

Comments
 (0)