From 828f312294f9bf90c0dce8551d95c92134db2262 Mon Sep 17 00:00:00 2001 From: Zack Date: Thu, 11 Apr 2024 14:47:34 +0800 Subject: [PATCH] feat: workspace invite (#4827) * feat: add rename and change icon for workspace cloud api * feat: added created at field for UserWorkspacePB * test: added view check after creating workspace * fix: allow new_icon to be empty string * feat: add invitation api cloud services * chore: cargo clippy * chore: merge conflict * feat: add workspace invitation test * fix: use back old role and af role * chore: use 1.75 channel rust toolchain * chore: added error for test case * chore: added ci var * chore: cargo clippy --- .github/workflows/rust_ci.yaml | 3 + frontend/rust-lib/Cargo.lock | 18 +---- .../event-integration/src/folder_event.rs | 66 +++++++++++++++-- .../tests/user/af_cloud_test/member_test.rs | 29 ++++++++ .../src/services/calculations/service.rs | 2 +- .../af_cloud/impls/user/cloud_service_impl.rs | 73 ++++++++++++++++++- .../src/af_cloud/impls/user/dto.rs | 27 ++++++- frontend/rust-lib/flowy-user-pub/src/cloud.rs | 23 +++++- .../rust-lib/flowy-user-pub/src/entities.rs | 18 +++++ .../flowy-user/src/entities/workspace.rs | 62 +++++++++++++++- .../rust-lib/flowy-user/src/event_handler.rs | 38 ++++++++++ frontend/rust-lib/flowy-user/src/event_map.rs | 16 +++- .../user_manager/manager_user_workspace.rs | 38 +++++++++- 13 files changed, 379 insertions(+), 34 deletions(-) diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index 7251939de6..a9011f4360 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -78,6 +78,9 @@ jobs: cp deploy.env .env sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env + sed -i 's|GOTRUE_SMTP_ADMIN_EMAIL=.*|GOTRUE_SMTP_ADMIN_EMAIL=${{ secrets.CI_TEST_GOTRUE_SMTP_ADMIN_EMAIL }}|' .env + sed -i 's|GOTRUE_SMTP_USER=.*|GOTRUE_SMTP_USER=${{ secrets.CI_TEST_GOTRUE_SMTP_USER }}|' .env + sed -i 's|GOTRUE_SMTP_PASS=.*|GOTRUE_SMTP_PASS=${{ secrets.CI_TEST_GOTRUE_SMTP_PASS }}|' .env - name: Run Docker-Compose working-directory: AppFlowy-Cloud diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index c5dc6d15d3..860f9df235 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -1158,7 +1158,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.11.2", + "phf 0.8.0", "smallvec", ] @@ -3643,7 +3643,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros 0.8.0", + "phf_macros", "phf_shared 0.8.0", "proc-macro-hack", ] @@ -3663,7 +3663,6 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ - "phf_macros 0.11.2", "phf_shared 0.11.2", ] @@ -3731,19 +3730,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.47", -] - [[package]] name = "phf_shared" version = "0.8.0" diff --git a/frontend/rust-lib/event-integration/src/folder_event.rs b/frontend/rust-lib/event-integration/src/folder_event.rs index 604bd1475d..4471c76775 100644 --- a/frontend/rust-lib/event-integration/src/folder_event.rs +++ b/frontend/rust-lib/event-integration/src/folder_event.rs @@ -3,36 +3,84 @@ use flowy_folder::entities::*; use flowy_folder::event_map::FolderEvent; use flowy_folder::event_map::FolderEvent::*; use flowy_user::entities::{ - AddWorkspaceMemberPB, QueryWorkspacePB, RemoveWorkspaceMemberPB, RepeatedWorkspaceMemberPB, + AcceptWorkspaceInvitationPB, AddWorkspaceMemberPB, QueryWorkspacePB, RemoveWorkspaceMemberPB, + RepeatedWorkspaceInvitationPB, RepeatedWorkspaceMemberPB, WorkspaceMemberInvitationPB, WorkspaceMemberPB, }; use flowy_user::errors::FlowyError; use flowy_user::event_map::UserEvent; +use flowy_user_pub::entities::Role; use crate::event_builder::EventBuilder; use crate::EventIntegrationTest; impl EventIntegrationTest { pub async fn add_workspace_member(&self, workspace_id: &str, email: &str) { - EventBuilder::new(self.clone()) + if let Some(err) = EventBuilder::new(self.clone()) .event(UserEvent::AddWorkspaceMember) .payload(AddWorkspaceMemberPB { workspace_id: workspace_id.to_string(), email: email.to_string(), }) .async_send() - .await; + .await + .error() + { + panic!("Add workspace member failed: {:?}", err); + } + } + + pub async fn invite_workspace_member(&self, workspace_id: &str, email: &str, role: Role) { + if let Some(err) = EventBuilder::new(self.clone()) + .event(UserEvent::InviteWorkspaceMember) + .payload(WorkspaceMemberInvitationPB { + workspace_id: workspace_id.to_string(), + invitee_email: email.to_string(), + role: role.into(), + }) + .async_send() + .await + .error() + { + panic!("Invite workspace member failed: {:?}", err); + } + } + + pub async fn list_workspace_invitations(&self) -> RepeatedWorkspaceInvitationPB { + EventBuilder::new(self.clone()) + .event(UserEvent::ListWorkspaceInvitations) + .async_send() + .await + .parse() + } + + pub async fn accept_workspace_invitation(&self, invitation_id: &str) { + if let Some(err) = EventBuilder::new(self.clone()) + .event(UserEvent::AcceptWorkspaceInvitation) + .payload(AcceptWorkspaceInvitationPB { + invite_id: invitation_id.to_string(), + }) + .async_send() + .await + .error() + { + panic!("Accept workspace invitation failed: {:?}", err) + }; } pub async fn delete_workspace_member(&self, workspace_id: &str, email: &str) { - EventBuilder::new(self.clone()) + if let Some(err) = EventBuilder::new(self.clone()) .event(UserEvent::RemoveWorkspaceMember) .payload(RemoveWorkspaceMemberPB { workspace_id: workspace_id.to_string(), email: email.to_string(), }) .async_send() - .await; + .await + .error() + { + panic!("Delete workspace member failed: {:?}", err) + }; } pub async fn get_workspace_members(&self, workspace_id: &str) -> Vec { @@ -78,11 +126,15 @@ impl EventIntegrationTest { }; // delete the view. the view will be moved to trash - EventBuilder::new(self.clone()) + if let Some(err) = EventBuilder::new(self.clone()) .event(FolderEvent::DeleteView) .payload(payload) .async_send() - .await; + .await + .error() + { + panic!("Delete view failed: {:?}", err) + }; } pub async fn update_view(&self, changeset: UpdateViewPayloadPB) -> Option { diff --git a/frontend/rust-lib/event-integration/tests/user/af_cloud_test/member_test.rs b/frontend/rust-lib/event-integration/tests/user/af_cloud_test/member_test.rs index 3f132234dd..f553362155 100644 --- a/frontend/rust-lib/event-integration/tests/user/af_cloud_test/member_test.rs +++ b/frontend/rust-lib/event-integration/tests/user/af_cloud_test/member_test.rs @@ -1,6 +1,35 @@ use crate::user::af_cloud_test::util::get_synced_workspaces; use event_integration::user_event::user_localhost_af_cloud; use event_integration::EventIntegrationTest; +use flowy_user_pub::entities::Role; + +#[tokio::test] +async fn af_cloud_invite_workspace_member() { + user_localhost_af_cloud().await; + let test_1 = EventIntegrationTest::new().await; + let user_1 = test_1.af_cloud_sign_up().await; + + let test_2 = EventIntegrationTest::new().await; + let user_2 = test_2.af_cloud_sign_up().await; + + test_1 + .invite_workspace_member(&user_1.workspace_id, &user_2.email, Role::Member) + .await; + + let invitations = test_2.list_workspace_invitations().await; + let target_invi = invitations + .items + .into_iter() + .find(|i| i.inviter_name == user_1.name && i.workspace_id == user_1.workspace_id) + .unwrap(); + + test_2 + .accept_workspace_invitation(&target_invi.invite_id) + .await; + + let workspaces = get_synced_workspaces(&test_2, user_2.id).await; + assert_eq!(workspaces.len(), 2); +} #[tokio::test] async fn af_cloud_add_workspace_member_test() { diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs index 9c0c1b1713..3ba34eb4b6 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs @@ -69,7 +69,7 @@ impl CalculationsService { } } - fn median(array: &Vec) -> f64 { + fn median(array: &[f64]) -> f64 { if (array.len() % 2) == 0 { let left = array.len() / 2 - 1; let right = array.len() / 2; diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs index fa045068da..fc76482fc9 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/cloud_service_impl.rs @@ -4,18 +4,22 @@ use std::sync::Arc; use anyhow::anyhow; use client_api::entity::workspace_dto::{ CreateWorkspaceMember, CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset, + WorkspaceMemberInvitation, }; use client_api::entity::{ - AFRole, AFWorkspace, AuthProvider, CollabParams, CreateCollabParams, QueryCollab, - QueryCollabParams, + AFRole, AFWorkspace, AFWorkspaceInvitation, AuthProvider, CollabParams, CreateCollabParams, }; +use client_api::entity::{QueryCollab, QueryCollabParams}; use client_api::{Client, ClientConfiguration}; use collab_entity::{CollabObject, CollabType}; use parking_lot::RwLock; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_user_pub::cloud::{UserCloudService, UserCollabParams, UserUpdate, UserUpdateReceiver}; -use flowy_user_pub::entities::*; +use flowy_user_pub::entities::{ + AFCloudOAuthParams, AuthResponse, Role, UpdateUserProfileParams, UserCredentials, UserProfile, + UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember, +}; use lib_infra::box_any::BoxAny; use lib_infra::future::FutureResult; use uuid::Uuid; @@ -27,6 +31,8 @@ use crate::af_cloud::impls::user::dto::{ use crate::af_cloud::impls::user::util::encryption_type_from_profile; use crate::af_cloud::{AFCloudClient, AFServer}; +use super::dto::{from_af_workspace_invitation_status, to_workspace_invitation_status}; + pub(crate) struct AFCloudUserAuthServiceImpl { server: T, user_change_recv: RwLock>>, @@ -197,6 +203,55 @@ where }) } + fn invite_workspace_member( + &self, + invitee_email: String, + workspace_id: String, + role: Role, + ) -> FutureResult<(), FlowyError> { + let try_get_client = self.server.try_get_client(); + FutureResult::new(async move { + try_get_client? + .invite_workspace_members( + &workspace_id, + vec![WorkspaceMemberInvitation { + email: invitee_email, + role: to_af_role(role), + }], + ) + .await?; + Ok(()) + }) + } + + fn list_workspace_invitations( + &self, + filter: Option, + ) -> FutureResult, FlowyError> { + let try_get_client = self.server.try_get_client(); + let filter = filter.map(to_workspace_invitation_status); + + FutureResult::new(async move { + let r = try_get_client? + .list_workspace_invitations(filter) + .await? + .into_iter() + .map(to_workspace_invitation) + .collect(); + Ok(r) + }) + } + + fn accept_workspace_invitations(&self, invite_id: String) -> FutureResult<(), FlowyError> { + let try_get_client = self.server.try_get_client(); + FutureResult::new(async move { + try_get_client? + .accept_workspace_invitation(&invite_id) + .await?; + Ok(()) + }) + } + fn remove_workspace_member( &self, user_email: String, @@ -451,6 +506,18 @@ fn to_user_workspaces(workspaces: Vec) -> Result Ok(result) } +fn to_workspace_invitation(invi: AFWorkspaceInvitation) -> WorkspaceInvitation { + WorkspaceInvitation { + invite_id: invi.invite_id, + workspace_id: invi.workspace_id, + workspace_name: invi.workspace_name, + inviter_email: invi.inviter_email, + inviter_name: invi.inviter_name, + status: from_af_workspace_invitation_status(invi.status), + updated_at: invi.updated_at, + } +} + fn oauth_params_from_box_any(any: BoxAny) -> Result { let map: HashMap = any.unbox_or_error()?; let sign_in_url = map diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/dto.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/dto.rs index 689a3fc931..c24ddbb51e 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/dto.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user/dto.rs @@ -1,10 +1,11 @@ use anyhow::Error; use client_api::entity::auth_dto::{UpdateUserParams, UserMetaData}; -use client_api::entity::{AFRole, AFUserProfile, AFWorkspaceMember}; +use client_api::entity::{AFRole, AFUserProfile, AFWorkspaceInvitationStatus, AFWorkspaceMember}; use flowy_user_pub::entities::{ - Authenticator, Role, UpdateUserProfileParams, UserProfile, WorkspaceMember, - USER_METADATA_ICON_URL, USER_METADATA_OPEN_AI_KEY, USER_METADATA_STABILITY_AI_KEY, + Authenticator, Role, UpdateUserProfileParams, UserProfile, WorkspaceInvitationStatus, + WorkspaceMember, USER_METADATA_ICON_URL, USER_METADATA_OPEN_AI_KEY, + USER_METADATA_STABILITY_AI_KEY, }; use crate::af_cloud::impls::user::util::encryption_type_from_profile; @@ -90,3 +91,23 @@ pub fn from_af_workspace_member(member: AFWorkspaceMember) -> WorkspaceMember { name: member.name, } } + +pub fn to_workspace_invitation_status( + status: WorkspaceInvitationStatus, +) -> AFWorkspaceInvitationStatus { + match status { + WorkspaceInvitationStatus::Pending => AFWorkspaceInvitationStatus::Pending, + WorkspaceInvitationStatus::Accepted => AFWorkspaceInvitationStatus::Accepted, + WorkspaceInvitationStatus::Rejected => AFWorkspaceInvitationStatus::Rejected, + } +} + +pub fn from_af_workspace_invitation_status( + status: AFWorkspaceInvitationStatus, +) -> WorkspaceInvitationStatus { + match status { + AFWorkspaceInvitationStatus::Pending => WorkspaceInvitationStatus::Pending, + AFWorkspaceInvitationStatus::Accepted => WorkspaceInvitationStatus::Accepted, + AFWorkspaceInvitationStatus::Rejected => WorkspaceInvitationStatus::Rejected, + } +} diff --git a/frontend/rust-lib/flowy-user-pub/src/cloud.rs b/frontend/rust-lib/flowy-user-pub/src/cloud.rs index 45337634f2..30eb3a601c 100644 --- a/frontend/rust-lib/flowy-user-pub/src/cloud.rs +++ b/frontend/rust-lib/flowy-user-pub/src/cloud.rs @@ -14,7 +14,7 @@ use uuid::Uuid; use crate::entities::{ AuthResponse, Authenticator, Role, UpdateUserProfileParams, UserCredentials, UserProfile, - UserTokenState, UserWorkspace, WorkspaceMember, + UserTokenState, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember, }; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -180,6 +180,7 @@ pub trait UserCloudService: Send + Sync + 'static { /// Deletes a workspace owned by the user. fn delete_workspace(&self, workspace_id: &str) -> FutureResult<(), FlowyError>; + // Deprecated, use invite instead fn add_workspace_member( &self, user_email: String, @@ -188,6 +189,26 @@ pub trait UserCloudService: Send + Sync + 'static { FutureResult::new(async { Ok(()) }) } + fn invite_workspace_member( + &self, + invitee_email: String, + workspace_id: String, + role: Role, + ) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } + + fn list_workspace_invitations( + &self, + filter: Option, + ) -> FutureResult, FlowyError> { + FutureResult::new(async { Ok(vec![]) }) + } + + fn accept_workspace_invitations(&self, invite_id: String) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } + fn remove_workspace_member( &self, user_email: String, diff --git a/frontend/rust-lib/flowy-user-pub/src/entities.rs b/frontend/rust-lib/flowy-user-pub/src/entities.rs index e722e5f979..16a2607c31 100644 --- a/frontend/rust-lib/flowy-user-pub/src/entities.rs +++ b/frontend/rust-lib/flowy-user-pub/src/entities.rs @@ -383,6 +383,7 @@ pub enum UserTokenState { Invalid, } +// Workspace Role #[derive(Clone, Debug)] pub enum Role { Owner, @@ -403,3 +404,20 @@ pub fn user_awareness_object_id(user_uuid: &Uuid, workspace_id: &str) -> Uuid { format!("user_awareness:{}", workspace_id).as_bytes(), ) } + +#[derive(Clone, Debug)] +pub enum WorkspaceInvitationStatus { + Pending, + Accepted, + Rejected, +} + +pub struct WorkspaceInvitation { + pub invite_id: Uuid, + pub workspace_id: Uuid, + pub workspace_name: Option, + pub inviter_email: Option, + pub inviter_name: Option, + pub status: WorkspaceInvitationStatus, + pub updated_at: DateTime, +} diff --git a/frontend/rust-lib/flowy-user/src/entities/workspace.rs b/frontend/rust-lib/flowy-user/src/entities/workspace.rs index c98e256547..cdbd928fe0 100644 --- a/frontend/rust-lib/flowy-user/src/entities/workspace.rs +++ b/frontend/rust-lib/flowy-user/src/entities/workspace.rs @@ -1,7 +1,7 @@ use validator::Validate; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_user_pub::entities::{Role, WorkspaceMember}; +use flowy_user_pub::entities::{Role, WorkspaceInvitation, WorkspaceMember}; use lib_infra::validator_fn::required_not_empty_str; #[derive(ProtoBuf, Default, Clone)] @@ -32,6 +32,65 @@ pub struct RepeatedWorkspaceMemberPB { pub items: Vec, } +#[derive(ProtoBuf, Default, Clone, Validate)] +pub struct WorkspaceMemberInvitationPB { + #[pb(index = 1)] + #[validate(custom = "required_not_empty_str")] + pub workspace_id: String, + + #[pb(index = 2)] + #[validate(email)] + pub invitee_email: String, + + #[pb(index = 3)] + pub role: AFRolePB, +} + +#[derive(Debug, ProtoBuf, Default, Clone)] +pub struct RepeatedWorkspaceInvitationPB { + #[pb(index = 1)] + pub items: Vec, +} + +#[derive(Debug, ProtoBuf, Default, Clone)] +pub struct WorkspaceInvitationPB { + #[pb(index = 1)] + pub invite_id: String, + #[pb(index = 2)] + pub workspace_id: String, + #[pb(index = 3)] + pub workspace_name: String, + #[pb(index = 4)] + pub inviter_email: String, + #[pb(index = 5)] + pub inviter_name: String, + #[pb(index = 6)] + pub status: String, + #[pb(index = 7)] + pub updated_at_timestamp: i64, +} + +impl From for WorkspaceInvitationPB { + fn from(value: WorkspaceInvitation) -> Self { + Self { + invite_id: value.invite_id.to_string(), + workspace_id: value.workspace_id.to_string(), + workspace_name: value.workspace_name.unwrap_or_default(), + inviter_email: value.inviter_email.unwrap_or_default(), + inviter_name: value.inviter_name.unwrap_or_default(), + status: format!("{:?}", value.status), + updated_at_timestamp: value.updated_at.timestamp(), + } + } +} + +#[derive(ProtoBuf, Default, Clone, Validate)] +pub struct AcceptWorkspaceInvitationPB { + #[pb(index = 1)] + #[validate(custom = "required_not_empty_str")] + pub invite_id: String, +} + #[derive(ProtoBuf, Default, Clone, Validate)] pub struct AddWorkspaceMemberPB { #[pb(index = 1)] @@ -75,6 +134,7 @@ pub struct UpdateWorkspaceMemberPB { pub role: AFRolePB, } +// Workspace Role #[derive(ProtoBuf_Enum, Clone, Default)] pub enum AFRolePB { Owner = 0, diff --git a/frontend/rust-lib/flowy-user/src/event_handler.rs b/frontend/rust-lib/flowy-user/src/event_handler.rs index def236828a..1cd15cc467 100644 --- a/frontend/rust-lib/flowy-user/src/event_handler.rs +++ b/frontend/rust-lib/flowy-user/src/event_handler.rs @@ -713,6 +713,44 @@ pub async fn change_workspace_icon_handler( } #[tracing::instrument(level = "debug", skip_all, err)] +pub async fn invite_workspace_member_handler( + param: AFPluginData, + manager: AFPluginState>, +) -> Result<(), FlowyError> { + let param = param.try_into_inner()?; + let manager = upgrade_manager(manager)?; + manager + .invite_member_to_workspace(param.workspace_id, param.invitee_email, param.role.into()) + .await?; + Ok(()) +} + +#[tracing::instrument(level = "debug", skip_all, err)] +pub async fn list_workspace_invitations_handler( + manager: AFPluginState>, +) -> DataResult { + let manager = upgrade_manager(manager)?; + let invitations = manager.list_pending_workspace_invitations().await?; + let invitations_pb: Vec = invitations + .into_iter() + .map(WorkspaceInvitationPB::from) + .collect(); + data_result_ok(RepeatedWorkspaceInvitationPB { + items: invitations_pb, + }) +} + +#[tracing::instrument(level = "debug", skip_all, err)] +pub async fn accept_workspace_invitations_handler( + param: AFPluginData, + manager: AFPluginState>, +) -> Result<(), FlowyError> { + let invite_id = param.try_into_inner()?.invite_id; + let manager = upgrade_manager(manager)?; + manager.accept_workspace_invitation(invite_id).await?; + Ok(()) +} + pub async fn leave_workspace_handler( param: AFPluginData, manager: AFPluginState>, diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 4bbf191f6c..a9fe6cbdad 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -54,7 +54,9 @@ pub fn init(user_manager: Weak) -> AFPlugin { .event(UserEvent::GetNotificationSettings, get_notification_settings) .event(UserEvent::ImportAppFlowyDataFolder, import_appflowy_data_folder_handler) // Workspace member - .event(UserEvent::AddWorkspaceMember, add_workspace_member_handler) + .event(UserEvent::AddWorkspaceMember, add_workspace_member_handler) // deprecated, use invite + // instead + .event(UserEvent::RemoveWorkspaceMember, delete_workspace_member_handler) .event(UserEvent::GetWorkspaceMember, get_workspace_member_handler) .event(UserEvent::UpdateWorkspaceMember, update_workspace_member_handler) @@ -65,6 +67,9 @@ pub fn init(user_manager: Weak) -> AFPlugin { .event(UserEvent::RenameWorkspace, rename_workspace_handler) .event(UserEvent::ChangeWorkspaceIcon, change_workspace_icon_handler) .event(UserEvent::LeaveWorkspace, leave_workspace_handler) + .event(UserEvent::InviteWorkspaceMember, invite_workspace_member_handler) + .event(UserEvent::ListWorkspaceInvitations, list_workspace_invitations_handler) + .event(UserEvent::AcceptWorkspaceInvitation, accept_workspace_invitations_handler) } #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] @@ -212,6 +217,15 @@ pub enum UserEvent { #[event(input = "UserWorkspaceIdPB")] LeaveWorkspace = 46, + + #[event(input = "WorkspaceMemberInvitationPB")] + InviteWorkspaceMember = 47, + + #[event(output = "RepeatedWorkspaceInvitationPB")] + ListWorkspaceInvitations = 48, + + #[event(input = "AcceptWorkspaceInvitationPB")] + AcceptWorkspaceInvitation = 49, } pub trait UserStatusCallback: Send + Sync + 'static { diff --git a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs index f49cee3a87..26d73be7d4 100644 --- a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs +++ b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs @@ -9,7 +9,9 @@ use flowy_error::{FlowyError, FlowyResult}; use flowy_folder_pub::entities::{AppFlowyData, ImportData}; use flowy_sqlite::schema::user_workspace_table; use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods}; -use flowy_user_pub::entities::{Role, UserWorkspace, WorkspaceMember}; +use flowy_user_pub::entities::{ + Role, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember, +}; use lib_dispatch::prelude::af_spawn; use crate::entities::{RepeatedUserWorkspacePB, ResetWorkspacePB}; @@ -230,6 +232,40 @@ impl UserManager { Ok(()) } + pub async fn invite_member_to_workspace( + &self, + workspace_id: String, + invitee_email: String, + role: Role, + ) -> FlowyResult<()> { + self + .cloud_services + .get_user_service()? + .invite_workspace_member(invitee_email, workspace_id, role) + .await?; + Ok(()) + } + + pub async fn list_pending_workspace_invitations(&self) -> FlowyResult> { + let status = Some(WorkspaceInvitationStatus::Pending); + let invitations = self + .cloud_services + .get_user_service()? + .list_workspace_invitations(status) + .await?; + Ok(invitations) + } + + pub async fn accept_workspace_invitation(&self, invite_id: String) -> FlowyResult<()> { + self + .cloud_services + .get_user_service()? + .accept_workspace_invitations(invite_id) + .await?; + Ok(()) + } + + // deprecated, use invite instead pub async fn add_workspace_member( &self, user_email: String,