Compare commits

...
14 changed files with 314 additions and 44 deletions

No files matched your search

+1 -1
View File
@@ -1202,7 +1202,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa 1.0.6",
"phf 0.11.2",
"phf 0.8.0",
"smallvec",
]
+2 -16
View File
@@ -901,7 +901,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.11.2",
"phf 0.8.0",
"smallvec",
]
@@ -2780,7 +2780,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",
]
@@ -2800,7 +2800,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",
]
@@ -2868,19 +2867,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.48",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
+2 -16
View File
@@ -1104,7 +1104,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.11.2",
"phf 0.8.0",
"smallvec",
]
@@ -3642,7 +3642,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",
]
@@ -3662,7 +3662,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",
]
@@ -3730,19 +3729,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"
@@ -13,6 +13,8 @@ use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
// Deprecated, use invite instead
pub async fn add_workspace_member(&self, workspace_id: &str, email: &str) {
EventBuilder::new(self.clone())
.event(UserEvent::AddWorkspaceMember)
@@ -24,6 +26,18 @@ impl EventIntegrationTest {
.await;
}
// Deprecated, use invite instead
pub async fn invite_workspace_member(&self, workspace_id: &str, email: &str) {
EventBuilder::new(self.clone())
.event(UserEvent::InviteWorkspaceMembers)
.payload(AddWorkspaceMemberPB {
workspace_id: workspace_id.to_string(),
email: email.to_string(),
})
.async_send()
.await;
}
pub async fn delete_workspace_member(&self, workspace_id: &str, email: &str) {
EventBuilder::new(self.clone())
.event(UserEvent::RemoveWorkspaceMember)
@@ -1,6 +1,7 @@
use event_integration::user_event::user_localhost_af_cloud;
use event_integration::EventIntegrationTest;
// Deprecated, use invite instead
#[tokio::test]
async fn af_cloud_add_workspace_member_test() {
user_localhost_af_cloud().await;
@@ -14,6 +15,8 @@ async fn af_cloud_add_workspace_member_test() {
assert_eq!(members.len(), 1);
assert_eq!(members[0].email, user_1.email);
test_1
.add_workspace_member(&user_1.workspace_id, &user_2.email)
.await;
@@ -24,6 +27,7 @@ async fn af_cloud_add_workspace_member_test() {
assert_eq!(members[1].email, user_2.email);
}
// Deprecated, use invite instead
#[tokio::test]
async fn af_cloud_delete_workspace_member_test() {
user_localhost_af_cloud().await;
@@ -4,8 +4,11 @@ use std::sync::Arc;
use anyhow::{anyhow, Error};
use client_api::entity::workspace_dto::{
CreateWorkspaceMember, CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset,
WorkspaceMemberInvitation,
};
use client_api::entity::{
AFRole, AFWorkspace, AFWorkspaceInvitation, AuthProvider, CollabParams, CreateCollabParams,
};
use client_api::entity::{AFRole, AFWorkspace, AuthProvider, CollabParams, CreateCollabParams};
use client_api::{Client, ClientConfiguration};
use collab::core::collab::CollabDocState;
use collab_entity::CollabObject;
@@ -13,7 +16,10 @@ 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;
@@ -25,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<T> {
server: T,
user_change_recv: RwLock<Option<tokio::sync::mpsc::Receiver<UserUpdate>>>,
@@ -173,6 +181,7 @@ where
})
}
// Deprecated, use invite instead
fn add_workspace_member(
&self,
user_email: String,
@@ -193,6 +202,55 @@ where
})
}
fn invite_workspace_member(
&self,
invitee_email: String,
workspace_id: String,
role: Role,
) -> FutureResult<(), Error> {
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>, Error> {
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<(), Error> {
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,
@@ -420,6 +478,18 @@ fn to_user_workspaces(workspaces: Vec<AFWorkspace>) -> Result<Vec<UserWorkspace>
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, Error> {
let map: HashMap<String, String> = any.unbox_or_error()?;
let sign_in_url = map
@@ -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,
}
}
@@ -259,7 +259,7 @@ fn spawn_ws_conn(
tokio::time::sleep(Duration::from_secs(2)).await;
event!(tracing::Level::INFO, "🟢reconnecting websocket");
let _ = ws_client.connect(&api_client.ws_addr(), conn_info).await;
let _ = ws_client.connect(api_client.ws_addr(), conn_info).await;
},
Err(err) => error!("Failed to get ws url: {}, connect state:{:?}", err, state),
}
@@ -291,7 +291,7 @@ fn spawn_ws_conn(
{
match api_client.ws_connect_info().await {
Ok(conn_info) => {
let _ = ws_client.connect(&api_client.ws_addr(), conn_info).await;
let _ = ws_client.connect(api_client.ws_addr(), conn_info).await;
},
Err(err) => error!("Failed to get ws url: {}", err),
}
+22 -1
View File
@@ -16,7 +16,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)]
@@ -182,6 +182,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,
@@ -190,6 +191,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<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn list_workspace_invitations(
&self,
filter: Option<WorkspaceInvitationStatus>,
) -> FutureResult<Vec<WorkspaceInvitation>, Error> {
FutureResult::new(async { Ok(vec![]) })
}
fn accept_workspace_invitations(&self, invite_id: String) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn remove_workspace_member(
&self,
user_email: String,
@@ -396,3 +396,20 @@ pub struct WorkspaceMember {
pub fn awareness_oid_from_user_uuid(user_uuid: &Uuid) -> Uuid {
Uuid::new_v5(user_uuid, b"user_awareness")
}
#[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 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<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(ProtoBuf, Default, Clone)]
pub struct RepeatedWorkspaceInvitationPB {
#[pb(index = 1)]
pub items: Vec<WorkspaceInvitationPB>,
}
#[derive(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)]
pub struct AddWorkspaceMemberPB {
#[pb(index = 1)]
@@ -607,6 +607,7 @@ pub async fn update_reminder_event_handler(
Ok(())
}
// Deprecated, use invite instead
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn add_workspace_member_handler(
data: AFPluginData<AddWorkspaceMemberPB>,
@@ -709,3 +710,43 @@ pub async fn change_workspace_icon_handler(
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn invite_workspace_members_handler(
param: AFPluginData<WorkspaceMemberInvitationPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> {
let param = param.try_into_inner()?;
let manager = upgrade_manager(manager)?;
manager
.invite_workspace_members(param.workspace_id, param.invitee_email, param.role.into())
.await?;
todo!()
}
#[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(())
}
+16 -1
View File
@@ -54,7 +54,9 @@ pub fn init(user_manager: Weak<UserManager>) -> 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)
@@ -64,6 +66,10 @@ pub fn init(user_manager: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::DeleteWorkspace, delete_workspace_handler)
.event(UserEvent::RenameWorkspace, rename_workspace_handler)
.event(UserEvent::ChangeWorkspaceIcon, change_workspace_icon_handler)
.event(UserEvent::InviteWorkspaceMembers, invite_workspace_members_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)]
@@ -208,6 +214,15 @@ pub enum UserEvent {
#[event(input = "ChangeWorkspaceIconPB")]
ChangeWorkspaceIcon = 45,
#[event(input = "WorkspaceMemberInvitationPB")]
InviteWorkspaceMembers = 46,
#[event(output = "RepeatedWorkspaceInvitationPB")]
ListWorkspaceInvitations = 47,
#[event(input = "AcceptWorkspaceInvitationPB")]
AcceptWorkspaceInvitation = 48,
}
pub trait UserStatusCallback: Send + Sync + 'static {
@@ -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};
@@ -194,6 +196,40 @@ impl UserManager {
Ok(())
}
pub async fn invite_workspace_members(
&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(
&self,
user_email: String,