diff --git a/cli/src/cancel_invitation.rs b/cli/src/cancel_invitation.rs index 9c39fcec007..e4d75c8747b 100644 --- a/cli/src/cancel_invitation.rs +++ b/cli/src/cancel_invitation.rs @@ -24,7 +24,7 @@ pub struct CancelInvitation { } pub async fn cancel_invitation(cancel_invitation: CancelInvitation) -> anyhow::Result<()> { - load_device_and_run( + load_cmds_and_run( cancel_invitation.config_dir, cancel_invitation.device, |cmds, _| async move { diff --git a/cli/src/create_workspace.rs b/cli/src/create_workspace.rs new file mode 100644 index 00000000000..bb8af93276e --- /dev/null +++ b/cli/src/create_workspace.rs @@ -0,0 +1,44 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use clap::Args; +use std::path::PathBuf; + +use libparsec::{get_default_config_dir, EntryName}; + +use crate::utils::*; + +#[derive(Args)] +pub struct CreateWorkspace { + /// Parsec config directory + #[arg(short, long, default_value_os_t = get_default_config_dir())] + config_dir: PathBuf, + /// Device slughash + #[arg(short, long)] + device: Option, + /// New workspace name + #[arg(short, long)] + name: EntryName, +} + +pub async fn create_workspace(create_workspace: CreateWorkspace) -> anyhow::Result<()> { + let CreateWorkspace { + config_dir, + device, + name, + } = create_workspace; + + load_client_and_run(config_dir, device, |client| async move { + let handle = start_spinner("Creating workspace"); + + client.user_ops.create_workspace(name).await?; + + handle.done(); + + println!("Workspace has been created"); + + client.stop().await; + + Ok(()) + }) + .await +} diff --git a/cli/src/export_recovery_device.rs b/cli/src/export_recovery_device.rs index 84030e7d96c..ec1029d8b7b 100644 --- a/cli/src/export_recovery_device.rs +++ b/cli/src/export_recovery_device.rs @@ -26,7 +26,7 @@ pub async fn export_recovery_device( load_device_and_run( export_recovery_device.config_dir, export_recovery_device.device, - |_, device| async move { + |device| async move { let handle = start_spinner("Saving recovery device file"); let passphrase = save_recovery_device(&export_recovery_device.output, &device).await?; diff --git a/cli/src/greet_invitation.rs b/cli/src/greet_invitation.rs index 056f1c5b75e..6ee8edd6a8d 100644 --- a/cli/src/greet_invitation.rs +++ b/cli/src/greet_invitation.rs @@ -31,7 +31,7 @@ pub struct GreetInvitation { } pub async fn greet_invitation(greet_invitation: GreetInvitation) -> anyhow::Result<()> { - load_device_and_run( + load_cmds_and_run( greet_invitation.config_dir, greet_invitation.device, |cmds, device| async move { diff --git a/cli/src/invite_device.rs b/cli/src/invite_device.rs index c63c7efdbc6..67dab029bd8 100644 --- a/cli/src/invite_device.rs +++ b/cli/src/invite_device.rs @@ -21,7 +21,7 @@ pub struct InviteDevice { } pub async fn invite_device(invite_device: InviteDevice) -> anyhow::Result<()> { - load_device_and_run( + load_cmds_and_run( invite_device.config_dir, invite_device.device, |cmds, device| async move { diff --git a/cli/src/invite_user.rs b/cli/src/invite_user.rs index adbf7bd1a07..61718f03730 100644 --- a/cli/src/invite_user.rs +++ b/cli/src/invite_user.rs @@ -27,7 +27,7 @@ pub struct InviteUser { } pub async fn invite_user(invite_user: InviteUser) -> anyhow::Result<()> { - load_device_and_run( + load_cmds_and_run( invite_user.config_dir, invite_user.device, |cmds, device| async move { diff --git a/cli/src/list_invitations.rs b/cli/src/list_invitations.rs index eb3371dc029..8e867cc8c39 100644 --- a/cli/src/list_invitations.rs +++ b/cli/src/list_invitations.rs @@ -21,7 +21,7 @@ pub struct ListInvitations { } pub async fn list_invitations(list_invitations: ListInvitations) -> anyhow::Result<()> { - load_device_and_run( + load_cmds_and_run( list_invitations.config_dir, list_invitations.device, |cmds, _| async move { diff --git a/cli/src/main.rs b/cli/src/main.rs index 7fd25431a0f..20903b7f239 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,6 +4,7 @@ mod bootstrap_organization; mod cancel_invitation; mod claim_invitation; mod create_organization; +mod create_workspace; mod export_recovery_device; mod greet_invitation; mod import_recovery_device; @@ -41,6 +42,8 @@ enum Command { ClaimInvitation(claim_invitation::ClaimInvitation), /// Create new organization CreateOrganization(create_organization::CreateOrganization), + /// Create new workspace + CreateWorkspace(create_workspace::CreateWorkspace), /// Export recovery device ExportRecoveryDevice(export_recovery_device::ExportRecoveryDevice), /// Import recovery device @@ -88,6 +91,9 @@ async fn main() -> anyhow::Result<()> { Command::CreateOrganization(create_organization) => { create_organization::create_organization(create_organization).await } + Command::CreateWorkspace(create_workspace) => { + create_workspace::create_workspace(create_workspace).await + } Command::ExportRecoveryDevice(export_recovery_device) => { export_recovery_device::export_recovery_device(export_recovery_device).await } diff --git a/cli/src/tests.rs b/cli/src/tests.rs index 2549e2df3f6..d776bf42087 100644 --- a/cli/src/tests.rs +++ b/cli/src/tests.rs @@ -532,6 +532,32 @@ async fn list_invitations(tmp_path: TmpPath) { ))); } +#[rstest::rstest] +#[tokio::test] +async fn create_workspace(tmp_path: TmpPath) { + let tmp_path_str = tmp_path.to_str().unwrap(); + let config = get_testenv_config(); + let (url, [alice, ..], _) = run_local_organization(&tmp_path, None, config) + .await + .unwrap(); + + set_env(&tmp_path_str, &url); + + Command::cargo_bin("parsec_cli") + .unwrap() + .args([ + "create-workspace", + "--device", + &alice.slughash(), + "--name", + "new-workspace", + ]) + .assert() + .stdout(predicates::str::contains( + "Creating workspace\nWorkspace has been created", + )); +} + #[rstest::rstest] #[tokio::test] async fn invite_device_dance(tmp_path: TmpPath) { diff --git a/cli/src/utils.rs b/cli/src/utils.rs index 2e1342e0e85..40cc6f3389b 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -3,9 +3,10 @@ use std::{future::Future, path::PathBuf, sync::Arc}; use libparsec::{ - list_available_devices, load_device, AuthenticatedCmds, AvailableDevice, DeviceAccessStrategy, - DeviceFileType, DeviceLabel, HumanHandle, LocalDevice, Password, ProxyConfig, SASCode, - UserProfile, + internal::{Client, EventBus}, + list_available_devices, load_device, AuthenticatedCmds, AvailableDevice, ClientConfig, + DeviceAccessStrategy, DeviceFileType, DeviceLabel, HumanHandle, LocalDevice, Password, + ProxyConfig, SASCode, UserProfile, }; use terminal_spinners::{SpinnerBuilder, SpinnerHandle, DOTS}; @@ -34,7 +35,7 @@ pub async fn load_device_and_run( function: F, ) -> anyhow::Result<()> where - F: FnOnce(AuthenticatedCmds, Arc) -> Fut, + F: FnOnce(Arc) -> Fut, Fut: Future>, { let devices = list_available_devices(&config_dir).await; @@ -86,13 +87,7 @@ where } }; - let cmds = AuthenticatedCmds::new( - &config_dir, - device.clone(), - ProxyConfig::new_from_env()?, - )?; - - function(cmds, device.clone()).await?; + function(device).await?; } _ => { println!("Multiple devices found for `{device_slughash}`:"); @@ -108,6 +103,46 @@ where Ok(()) } +pub async fn load_cmds_and_run( + config_dir: PathBuf, + device_slughash: Option, + function: F, +) -> anyhow::Result<()> +where + F: FnOnce(AuthenticatedCmds, Arc) -> Fut, + Fut: Future>, +{ + load_device_and_run(config_dir.clone(), device_slughash, |device| async move { + let cmds = + AuthenticatedCmds::new(&config_dir, device.clone(), ProxyConfig::new_from_env()?)?; + + function(cmds, device).await + }) + .await +} + +pub async fn load_client_and_run( + config_dir: PathBuf, + device_slughash: Option, + function: F, +) -> anyhow::Result<()> +where + F: FnOnce(Client) -> Fut, + Fut: Future>, +{ + load_device_and_run(config_dir, device_slughash, |device| async move { + let client = Client::start( + Arc::new(ClientConfig::default().into()), + EventBus::default(), + device, + ) + .await?; + + function(client).await + }) + .await +} + pub fn start_spinner(text: &'static str) -> SpinnerHandle { SpinnerBuilder::new().spinner(&DOTS).text(text).start() } diff --git a/libparsec/src/lib.rs b/libparsec/src/lib.rs index 02cfff39e91..4ebcb5663a7 100644 --- a/libparsec/src/lib.rs +++ b/libparsec/src/lib.rs @@ -30,7 +30,7 @@ pub use workspace::*; pub mod internal { pub use libparsec_client::{ - claimer_retrieve_info, DeviceClaimFinalizeCtx, DeviceClaimInProgress1Ctx, + claimer_retrieve_info, Client, DeviceClaimFinalizeCtx, DeviceClaimInProgress1Ctx, DeviceClaimInProgress2Ctx, DeviceClaimInProgress3Ctx, DeviceClaimInitialCtx, DeviceGreetInProgress1Ctx, DeviceGreetInProgress2Ctx, DeviceGreetInProgress3Ctx, DeviceGreetInProgress4Ctx, DeviceGreetInitialCtx, EventBus, UserClaimFinalizeCtx,