mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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
This commit is contained in:
parent
b51ff318a1
commit
828f312294
3
.github/workflows/rust_ci.yaml
vendored
3
.github/workflows/rust_ci.yaml
vendored
@ -78,6 +78,9 @@ jobs:
|
|||||||
cp deploy.env .env
|
cp deploy.env .env
|
||||||
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .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|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
|
- name: Run Docker-Compose
|
||||||
working-directory: AppFlowy-Cloud
|
working-directory: AppFlowy-Cloud
|
||||||
|
18
frontend/rust-lib/Cargo.lock
generated
18
frontend/rust-lib/Cargo.lock
generated
@ -1158,7 +1158,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa",
|
"itoa",
|
||||||
"phf 0.11.2",
|
"phf 0.8.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3643,7 +3643,7 @@ version = "0.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_macros 0.8.0",
|
"phf_macros",
|
||||||
"phf_shared 0.8.0",
|
"phf_shared 0.8.0",
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
]
|
]
|
||||||
@ -3663,7 +3663,6 @@ version = "0.11.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_macros 0.11.2",
|
|
||||||
"phf_shared 0.11.2",
|
"phf_shared 0.11.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3731,19 +3730,6 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -3,36 +3,84 @@ use flowy_folder::entities::*;
|
|||||||
use flowy_folder::event_map::FolderEvent;
|
use flowy_folder::event_map::FolderEvent;
|
||||||
use flowy_folder::event_map::FolderEvent::*;
|
use flowy_folder::event_map::FolderEvent::*;
|
||||||
use flowy_user::entities::{
|
use flowy_user::entities::{
|
||||||
AddWorkspaceMemberPB, QueryWorkspacePB, RemoveWorkspaceMemberPB, RepeatedWorkspaceMemberPB,
|
AcceptWorkspaceInvitationPB, AddWorkspaceMemberPB, QueryWorkspacePB, RemoveWorkspaceMemberPB,
|
||||||
|
RepeatedWorkspaceInvitationPB, RepeatedWorkspaceMemberPB, WorkspaceMemberInvitationPB,
|
||||||
WorkspaceMemberPB,
|
WorkspaceMemberPB,
|
||||||
};
|
};
|
||||||
use flowy_user::errors::FlowyError;
|
use flowy_user::errors::FlowyError;
|
||||||
use flowy_user::event_map::UserEvent;
|
use flowy_user::event_map::UserEvent;
|
||||||
|
use flowy_user_pub::entities::Role;
|
||||||
|
|
||||||
use crate::event_builder::EventBuilder;
|
use crate::event_builder::EventBuilder;
|
||||||
use crate::EventIntegrationTest;
|
use crate::EventIntegrationTest;
|
||||||
|
|
||||||
impl EventIntegrationTest {
|
impl EventIntegrationTest {
|
||||||
pub async fn add_workspace_member(&self, workspace_id: &str, email: &str) {
|
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)
|
.event(UserEvent::AddWorkspaceMember)
|
||||||
.payload(AddWorkspaceMemberPB {
|
.payload(AddWorkspaceMemberPB {
|
||||||
workspace_id: workspace_id.to_string(),
|
workspace_id: workspace_id.to_string(),
|
||||||
email: email.to_string(),
|
email: email.to_string(),
|
||||||
})
|
})
|
||||||
.async_send()
|
.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) {
|
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)
|
.event(UserEvent::RemoveWorkspaceMember)
|
||||||
.payload(RemoveWorkspaceMemberPB {
|
.payload(RemoveWorkspaceMemberPB {
|
||||||
workspace_id: workspace_id.to_string(),
|
workspace_id: workspace_id.to_string(),
|
||||||
email: email.to_string(),
|
email: email.to_string(),
|
||||||
})
|
})
|
||||||
.async_send()
|
.async_send()
|
||||||
.await;
|
.await
|
||||||
|
.error()
|
||||||
|
{
|
||||||
|
panic!("Delete workspace member failed: {:?}", err)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_workspace_members(&self, workspace_id: &str) -> Vec<WorkspaceMemberPB> {
|
pub async fn get_workspace_members(&self, workspace_id: &str) -> Vec<WorkspaceMemberPB> {
|
||||||
@ -78,11 +126,15 @@ impl EventIntegrationTest {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// delete the view. the view will be moved to trash
|
// 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)
|
.event(FolderEvent::DeleteView)
|
||||||
.payload(payload)
|
.payload(payload)
|
||||||
.async_send()
|
.async_send()
|
||||||
.await;
|
.await
|
||||||
|
.error()
|
||||||
|
{
|
||||||
|
panic!("Delete view failed: {:?}", err)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_view(&self, changeset: UpdateViewPayloadPB) -> Option<FlowyError> {
|
pub async fn update_view(&self, changeset: UpdateViewPayloadPB) -> Option<FlowyError> {
|
||||||
|
@ -1,6 +1,35 @@
|
|||||||
use crate::user::af_cloud_test::util::get_synced_workspaces;
|
use crate::user::af_cloud_test::util::get_synced_workspaces;
|
||||||
use event_integration::user_event::user_localhost_af_cloud;
|
use event_integration::user_event::user_localhost_af_cloud;
|
||||||
use event_integration::EventIntegrationTest;
|
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]
|
#[tokio::test]
|
||||||
async fn af_cloud_add_workspace_member_test() {
|
async fn af_cloud_add_workspace_member_test() {
|
||||||
|
@ -69,7 +69,7 @@ impl CalculationsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn median(array: &Vec<f64>) -> f64 {
|
fn median(array: &[f64]) -> f64 {
|
||||||
if (array.len() % 2) == 0 {
|
if (array.len() % 2) == 0 {
|
||||||
let left = array.len() / 2 - 1;
|
let left = array.len() / 2 - 1;
|
||||||
let right = array.len() / 2;
|
let right = array.len() / 2;
|
||||||
|
@ -4,18 +4,22 @@ use std::sync::Arc;
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use client_api::entity::workspace_dto::{
|
use client_api::entity::workspace_dto::{
|
||||||
CreateWorkspaceMember, CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset,
|
CreateWorkspaceMember, CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset,
|
||||||
|
WorkspaceMemberInvitation,
|
||||||
};
|
};
|
||||||
use client_api::entity::{
|
use client_api::entity::{
|
||||||
AFRole, AFWorkspace, AuthProvider, CollabParams, CreateCollabParams, QueryCollab,
|
AFRole, AFWorkspace, AFWorkspaceInvitation, AuthProvider, CollabParams, CreateCollabParams,
|
||||||
QueryCollabParams,
|
|
||||||
};
|
};
|
||||||
|
use client_api::entity::{QueryCollab, QueryCollabParams};
|
||||||
use client_api::{Client, ClientConfiguration};
|
use client_api::{Client, ClientConfiguration};
|
||||||
use collab_entity::{CollabObject, CollabType};
|
use collab_entity::{CollabObject, CollabType};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
use flowy_user_pub::cloud::{UserCloudService, UserCollabParams, UserUpdate, UserUpdateReceiver};
|
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::box_any::BoxAny;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
use uuid::Uuid;
|
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::impls::user::util::encryption_type_from_profile;
|
||||||
use crate::af_cloud::{AFCloudClient, AFServer};
|
use crate::af_cloud::{AFCloudClient, AFServer};
|
||||||
|
|
||||||
|
use super::dto::{from_af_workspace_invitation_status, to_workspace_invitation_status};
|
||||||
|
|
||||||
pub(crate) struct AFCloudUserAuthServiceImpl<T> {
|
pub(crate) struct AFCloudUserAuthServiceImpl<T> {
|
||||||
server: T,
|
server: T,
|
||||||
user_change_recv: RwLock<Option<tokio::sync::mpsc::Receiver<UserUpdate>>>,
|
user_change_recv: RwLock<Option<tokio::sync::mpsc::Receiver<UserUpdate>>>,
|
||||||
@ -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<WorkspaceInvitationStatus>,
|
||||||
|
) -> FutureResult<Vec<WorkspaceInvitation>, 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(
|
fn remove_workspace_member(
|
||||||
&self,
|
&self,
|
||||||
user_email: String,
|
user_email: String,
|
||||||
@ -451,6 +506,18 @@ fn to_user_workspaces(workspaces: Vec<AFWorkspace>) -> Result<Vec<UserWorkspace>
|
|||||||
Ok(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<AFCloudOAuthParams, FlowyError> {
|
fn oauth_params_from_box_any(any: BoxAny) -> Result<AFCloudOAuthParams, FlowyError> {
|
||||||
let map: HashMap<String, String> = any.unbox_or_error()?;
|
let map: HashMap<String, String> = any.unbox_or_error()?;
|
||||||
let sign_in_url = map
|
let sign_in_url = map
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use client_api::entity::auth_dto::{UpdateUserParams, UserMetaData};
|
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::{
|
use flowy_user_pub::entities::{
|
||||||
Authenticator, Role, UpdateUserProfileParams, UserProfile, WorkspaceMember,
|
Authenticator, Role, UpdateUserProfileParams, UserProfile, WorkspaceInvitationStatus,
|
||||||
USER_METADATA_ICON_URL, USER_METADATA_OPEN_AI_KEY, USER_METADATA_STABILITY_AI_KEY,
|
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;
|
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,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
AuthResponse, Authenticator, Role, UpdateUserProfileParams, UserCredentials, UserProfile,
|
AuthResponse, Authenticator, Role, UpdateUserProfileParams, UserCredentials, UserProfile,
|
||||||
UserTokenState, UserWorkspace, WorkspaceMember,
|
UserTokenState, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -180,6 +180,7 @@ pub trait UserCloudService: Send + Sync + 'static {
|
|||||||
/// Deletes a workspace owned by the user.
|
/// Deletes a workspace owned by the user.
|
||||||
fn delete_workspace(&self, workspace_id: &str) -> FutureResult<(), FlowyError>;
|
fn delete_workspace(&self, workspace_id: &str) -> FutureResult<(), FlowyError>;
|
||||||
|
|
||||||
|
// Deprecated, use invite instead
|
||||||
fn add_workspace_member(
|
fn add_workspace_member(
|
||||||
&self,
|
&self,
|
||||||
user_email: String,
|
user_email: String,
|
||||||
@ -188,6 +189,26 @@ pub trait UserCloudService: Send + Sync + 'static {
|
|||||||
FutureResult::new(async { Ok(()) })
|
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<WorkspaceInvitationStatus>,
|
||||||
|
) -> FutureResult<Vec<WorkspaceInvitation>, FlowyError> {
|
||||||
|
FutureResult::new(async { Ok(vec![]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept_workspace_invitations(&self, invite_id: String) -> FutureResult<(), FlowyError> {
|
||||||
|
FutureResult::new(async { Ok(()) })
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_workspace_member(
|
fn remove_workspace_member(
|
||||||
&self,
|
&self,
|
||||||
user_email: String,
|
user_email: String,
|
||||||
|
@ -383,6 +383,7 @@ pub enum UserTokenState {
|
|||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workspace Role
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Role {
|
pub enum Role {
|
||||||
Owner,
|
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(),
|
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<String>,
|
||||||
|
pub inviter_email: Option<String>,
|
||||||
|
pub inviter_name: Option<String>,
|
||||||
|
pub status: WorkspaceInvitationStatus,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
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;
|
use lib_infra::validator_fn::required_not_empty_str;
|
||||||
|
|
||||||
#[derive(ProtoBuf, Default, Clone)]
|
#[derive(ProtoBuf, Default, Clone)]
|
||||||
@ -32,6 +32,65 @@ pub struct RepeatedWorkspaceMemberPB {
|
|||||||
pub items: Vec<WorkspaceMemberPB>,
|
pub items: Vec<WorkspaceMemberPB>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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<WorkspaceInvitationPB>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<WorkspaceInvitation> 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)]
|
#[derive(ProtoBuf, Default, Clone, Validate)]
|
||||||
pub struct AddWorkspaceMemberPB {
|
pub struct AddWorkspaceMemberPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
@ -75,6 +134,7 @@ pub struct UpdateWorkspaceMemberPB {
|
|||||||
pub role: AFRolePB,
|
pub role: AFRolePB,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workspace Role
|
||||||
#[derive(ProtoBuf_Enum, Clone, Default)]
|
#[derive(ProtoBuf_Enum, Clone, Default)]
|
||||||
pub enum AFRolePB {
|
pub enum AFRolePB {
|
||||||
Owner = 0,
|
Owner = 0,
|
||||||
|
@ -713,6 +713,44 @@ pub async fn change_workspace_icon_handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||||
|
pub async fn invite_workspace_member_handler(
|
||||||
|
param: AFPluginData<WorkspaceMemberInvitationPB>,
|
||||||
|
manager: AFPluginState<Weak<UserManager>>,
|
||||||
|
) -> 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<Weak<UserManager>>,
|
||||||
|
) -> DataResult<RepeatedWorkspaceInvitationPB, FlowyError> {
|
||||||
|
let manager = upgrade_manager(manager)?;
|
||||||
|
let invitations = manager.list_pending_workspace_invitations().await?;
|
||||||
|
let invitations_pb: Vec<WorkspaceInvitationPB> = 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<AcceptWorkspaceInvitationPB>,
|
||||||
|
manager: AFPluginState<Weak<UserManager>>,
|
||||||
|
) -> 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(
|
pub async fn leave_workspace_handler(
|
||||||
param: AFPluginData<UserWorkspaceIdPB>,
|
param: AFPluginData<UserWorkspaceIdPB>,
|
||||||
manager: AFPluginState<Weak<UserManager>>,
|
manager: AFPluginState<Weak<UserManager>>,
|
||||||
|
@ -54,7 +54,9 @@ pub fn init(user_manager: Weak<UserManager>) -> AFPlugin {
|
|||||||
.event(UserEvent::GetNotificationSettings, get_notification_settings)
|
.event(UserEvent::GetNotificationSettings, get_notification_settings)
|
||||||
.event(UserEvent::ImportAppFlowyDataFolder, import_appflowy_data_folder_handler)
|
.event(UserEvent::ImportAppFlowyDataFolder, import_appflowy_data_folder_handler)
|
||||||
// Workspace member
|
// 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::RemoveWorkspaceMember, delete_workspace_member_handler)
|
||||||
.event(UserEvent::GetWorkspaceMember, get_workspace_member_handler)
|
.event(UserEvent::GetWorkspaceMember, get_workspace_member_handler)
|
||||||
.event(UserEvent::UpdateWorkspaceMember, update_workspace_member_handler)
|
.event(UserEvent::UpdateWorkspaceMember, update_workspace_member_handler)
|
||||||
@ -65,6 +67,9 @@ pub fn init(user_manager: Weak<UserManager>) -> AFPlugin {
|
|||||||
.event(UserEvent::RenameWorkspace, rename_workspace_handler)
|
.event(UserEvent::RenameWorkspace, rename_workspace_handler)
|
||||||
.event(UserEvent::ChangeWorkspaceIcon, change_workspace_icon_handler)
|
.event(UserEvent::ChangeWorkspaceIcon, change_workspace_icon_handler)
|
||||||
.event(UserEvent::LeaveWorkspace, leave_workspace_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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||||
@ -212,6 +217,15 @@ pub enum UserEvent {
|
|||||||
|
|
||||||
#[event(input = "UserWorkspaceIdPB")]
|
#[event(input = "UserWorkspaceIdPB")]
|
||||||
LeaveWorkspace = 46,
|
LeaveWorkspace = 46,
|
||||||
|
|
||||||
|
#[event(input = "WorkspaceMemberInvitationPB")]
|
||||||
|
InviteWorkspaceMember = 47,
|
||||||
|
|
||||||
|
#[event(output = "RepeatedWorkspaceInvitationPB")]
|
||||||
|
ListWorkspaceInvitations = 48,
|
||||||
|
|
||||||
|
#[event(input = "AcceptWorkspaceInvitationPB")]
|
||||||
|
AcceptWorkspaceInvitation = 49,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UserStatusCallback: Send + Sync + 'static {
|
pub trait UserStatusCallback: Send + Sync + 'static {
|
||||||
|
@ -9,7 +9,9 @@ use flowy_error::{FlowyError, FlowyResult};
|
|||||||
use flowy_folder_pub::entities::{AppFlowyData, ImportData};
|
use flowy_folder_pub::entities::{AppFlowyData, ImportData};
|
||||||
use flowy_sqlite::schema::user_workspace_table;
|
use flowy_sqlite::schema::user_workspace_table;
|
||||||
use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
|
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 lib_dispatch::prelude::af_spawn;
|
||||||
|
|
||||||
use crate::entities::{RepeatedUserWorkspacePB, ResetWorkspacePB};
|
use crate::entities::{RepeatedUserWorkspacePB, ResetWorkspacePB};
|
||||||
@ -230,6 +232,40 @@ impl UserManager {
|
|||||||
Ok(())
|
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<Vec<WorkspaceInvitation>> {
|
||||||
|
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(
|
pub async fn add_workspace_member(
|
||||||
&self,
|
&self,
|
||||||
user_email: String,
|
user_email: String,
|
||||||
|
Loading…
Reference in New Issue
Block a user