diff --git a/cli/src/create_workspace.rs b/cli/src/create_workspace.rs index bb8af93276e..7cf75a80226 100644 --- a/cli/src/create_workspace.rs +++ b/cli/src/create_workspace.rs @@ -30,11 +30,11 @@ pub async fn create_workspace(create_workspace: CreateWorkspace) -> anyhow::Resu load_client_and_run(config_dir, device, |client| async move { let handle = start_spinner("Creating workspace"); - client.user_ops.create_workspace(name).await?; + let id = client.user_ops.create_workspace(name).await?.simple(); handle.done(); - println!("Workspace has been created"); + println!("Workspace has been created with id: {YELLOW}{id}{RESET}"); client.stop().await; diff --git a/cli/src/list_users.rs b/cli/src/list_users.rs index b1add1a5aa3..143a8809c54 100644 --- a/cli/src/list_users.rs +++ b/cli/src/list_users.rs @@ -28,7 +28,6 @@ pub async fn list_users(list_users: ListUsers) -> anyhow::Result<()> { } = list_users; load_client_and_run(config_dir, device, |client| async move { - client.user_ops.sync().await?; let users = client.certificates_ops.list_users(skip_revoked, None, None).await?; if users.is_empty() { diff --git a/cli/src/main.rs b/cli/src/main.rs index 6a9147db10e..2b58b19c05c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -17,6 +17,7 @@ mod list_workspaces; mod remove_device; #[cfg(feature = "testenv")] mod run_testenv; +mod share_workspace; mod stats_organization; mod stats_server; mod status_organization; @@ -72,6 +73,8 @@ enum Command { /// This command creates three users, `Alice`, `Bob` and `Toto`, /// To run testenv, see the script run_testenv in the current directory. RunTestenv(run_testenv::RunTestenv), + /// Share workspace + ShareWorkspace(share_workspace::ShareWorkspace), /// Get data & user statistics on organization StatsOrganization(stats_organization::StatsOrganization), /// Get a per-organization report of server usage @@ -122,6 +125,9 @@ async fn main() -> anyhow::Result<()> { Command::RemoveDevice(remove_device) => remove_device::remove_device(remove_device).await, #[cfg(feature = "testenv")] Command::RunTestenv(run_testenv) => run_testenv::run_testenv(run_testenv).await, + Command::ShareWorkspace(share_workspace) => { + share_workspace::share_workspace(share_workspace).await + } Command::StatsOrganization(stats_organization) => { stats_organization::stats_organization(stats_organization).await } diff --git a/cli/src/share_workspace.rs b/cli/src/share_workspace.rs new file mode 100644 index 00000000000..3ebe64cda17 --- /dev/null +++ b/cli/src/share_workspace.rs @@ -0,0 +1,55 @@ +// 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, RealmRole, UserID, VlobID}; + +use crate::utils::*; + +#[derive(Args)] +pub struct ShareWorkspace { + /// Parsec config directory + #[arg(short, long, default_value_os_t = get_default_config_dir())] + config_dir: PathBuf, + /// Device slughash + #[arg(short, long)] + device: Option, + /// Workspace id + #[arg(short, long, value_parser = VlobID::from_hex)] + workspace_id: VlobID, + /// Recipient id + #[arg(short, long)] + user_id: UserID, + /// Role (owner/manager/contributor/reader) + #[arg(short, long)] + role: RealmRole, +} + +pub async fn share_workspace(share_workspace: ShareWorkspace) -> anyhow::Result<()> { + let ShareWorkspace { + config_dir, + device, + workspace_id, + user_id, + role, + } = share_workspace; + + load_client_and_run(config_dir, device, |client| async move { + let handle = start_spinner("Sharing workspace"); + + client + .user_ops + .share_workspace(workspace_id, user_id, Some(role)) + .await?; + + handle.done(); + + println!("Workspace has been shared"); + + client.stop().await; + + Ok(()) + }) + .await +} diff --git a/cli/src/tests.rs b/cli/src/tests.rs index e6b1964216f..af18f3c8a28 100644 --- a/cli/src/tests.rs +++ b/cli/src/tests.rs @@ -11,8 +11,8 @@ use std::{ use libparsec::{ authenticated_cmds::latest::invite_new::{self, InviteNewRep, UserOrDevice}, get_default_config_dir, tmp_path, AuthenticatedCmds, BackendAddr, BackendInvitationAddr, - BackendOrganizationBootstrapAddr, ClientConfig, InvitationType, LocalDevice, OrganizationID, - ProxyConfig, TmpPath, PARSEC_CONFIG_DIR, PARSEC_DATA_DIR, PARSEC_HOME_DIR, + BackendOrganizationBootstrapAddr, ClientConfig, HumanHandle, InvitationType, LocalDevice, + OrganizationID, ProxyConfig, TmpPath, PARSEC_CONFIG_DIR, PARSEC_DATA_DIR, PARSEC_HOME_DIR, }; use crate::{ @@ -588,6 +588,68 @@ async fn list_users(tmp_path: TmpPath) { ); } +#[rstest::rstest] +#[tokio::test] +async fn share_workspace(tmp_path: TmpPath) { + let tmp_path_str = tmp_path.to_str().unwrap(); + let config = get_testenv_config(); + let (url, [alice, _, bob], _) = run_local_organization(&tmp_path, None, config) + .await + .unwrap(); + + set_env(&tmp_path_str, &url); + + load_client_and_run( + get_default_config_dir(), + Some(alice.slughash()), + |client| async move { + let wid = client + .user_ops + .create_workspace("new-workspace".parse().unwrap()) + .await?; + + let users = client + .certificates_ops + .list_users(false, None, None) + .await + .unwrap(); + let bob_id = &users + .iter() + .find(|x| x.human_handle == HumanHandle::new("bob@example.com", "Bob").unwrap()) + .unwrap() + .id; + + Command::cargo_bin("parsec_cli") + .unwrap() + .args([ + "share-workspace", + "--device", + &alice.slughash(), + "--workspace-id", + &wid.simple().to_string(), + "--user-id", + &bob_id.to_string(), + "--role", + "contributor", + ]) + .assert() + .stdout(predicates::str::contains( + "Sharing workspace\nWorkspace has been shared", + )); + + Ok(()) + }, + ) + .await + .unwrap(); + + Command::cargo_bin("parsec_cli") + .unwrap() + .args(["list-workspaces", "--device", &bob.slughash()]) + .assert() + .stdout(predicates::str::contains("new-workspace")); +} + #[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 40cc6f3389b..b6ab6b12f96 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -138,6 +138,8 @@ where ) .await?; + client.user_ops.sync().await?; + function(client).await }) .await diff --git a/libparsec/crates/types/src/manifest.rs b/libparsec/crates/types/src/manifest.rs index 988be551156..ffce43e9928 100644 --- a/libparsec/crates/types/src/manifest.rs +++ b/libparsec/crates/types/src/manifest.rs @@ -197,6 +197,20 @@ impl RealmRole { } } +impl std::str::FromStr for RealmRole { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "owner" => Ok(Self::Owner), + "manager" => Ok(Self::Manager), + "contributor" => Ok(Self::Contributor), + "reader" => Ok(Self::Reader), + _ => Err("Failed to parse RealmRole"), + } + } +} + /* * WorkspaceEntry */