test: Af cloud workspace member (#3779)

* chore: implement workspace memeber events

* chore: add workspace member test

* chore: fix fmt
This commit is contained in:
Nathan.fooo 2023-10-25 21:35:47 +08:00 committed by GitHub
parent ad21a61ffb
commit 48582cb718
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 620 additions and 262 deletions

View File

@ -421,7 +421,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@ -436,7 +436,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
ONLY_ACTIVE_ARCH = YES;
ONLY_ACTIVE_ARCH = false;
PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.appflowy.flutter;
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -558,7 +558,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@ -573,6 +573,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
ONLY_ACTIVE_ARCH = false;
PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.appflowy.flutter;
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -586,7 +587,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
@ -601,7 +602,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
ONLY_ACTIVE_ARCH = YES;
ONLY_ACTIVE_ARCH = false;
PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.appflowy.flutter;
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -762,7 +762,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"bytes",
@ -1292,7 +1292,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa 1.0.6",
"phf 0.8.0",
"phf 0.11.2",
"smallvec",
]
@ -1438,7 +1438,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"chrono",
@ -2117,6 +2117,7 @@ dependencies = [
"thiserror",
"tokio-postgres",
"url",
"validator",
]
[[package]]
@ -2781,7 +2782,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"futures-util",
@ -2797,7 +2798,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"jsonwebtoken",
@ -3232,7 +3233,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"reqwest",
@ -3451,6 +3452,7 @@ dependencies = [
"thread-id",
"tokio",
"tracing",
"validator",
]
[[package]]
@ -4307,6 +4309,7 @@ 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",
]
@ -4398,6 +4401,19 @@ 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.29",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
@ -4901,7 +4917,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"bytes",
"collab",
@ -5623,7 +5639,7 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"collab-entity",

View File

@ -38,7 +38,7 @@ custom-protocol = ["tauri/custom-protocol"]
# Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "cc6b451104e7154b38df5ae9c4e7215a61fcf172" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87d0f05e988a02e9272a42722b304289be320e4" }
# Please use the following script to update collab.
# Working directory: frontend
#

View File

@ -660,7 +660,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"bytes",
@ -1265,7 +1265,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"chrono",
@ -1940,6 +1940,7 @@ dependencies = [
"thiserror",
"tokio-postgres",
"url",
"validator",
]
[[package]]
@ -2440,7 +2441,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"futures-util",
@ -2456,7 +2457,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"jsonwebtoken",
@ -2816,7 +2817,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"reqwest",
@ -2951,6 +2952,7 @@ dependencies = [
"thread-id",
"tokio",
"tracing",
"validator",
]
[[package]]
@ -4251,7 +4253,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"bytes",
"collab",
@ -4872,7 +4874,7 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=cc6b451104e7154b38df5ae9c4e7215a61fcf172#cc6b451104e7154b38df5ae9c4e7215a61fcf172"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4"
dependencies = [
"anyhow",
"collab-entity",

View File

@ -82,7 +82,7 @@ incremental = false
# Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "cc6b451104e7154b38df5ae9c4e7215a61fcf172" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87d0f05e988a02e9272a42722b304289be320e4" }
# Please use the following script to update collab.
# Working directory: frontend
#

View File

@ -25,6 +25,7 @@ lib-infra = { path = "../../../shared-lib/lib-infra" }
[features]
default = []
supabase_integrate = ["collab-plugins/postgres_storage_plugin", "collab-plugins/rocksdb_plugin"]
appflowy_cloud_integrate = ["collab-plugins/rocksdb_plugin"]
snapshot_plugin = ["collab-plugins/snapshot_plugin"]
supabase_integrate = ["collab-plugins/postgres_storage_plugin", "rocksdb_plugin"]
appflowy_cloud_integrate = ["rocksdb_plugin"]
snapshot_plugin = ["collab-plugins/snapshot_plugin"]
rocksdb_plugin = ["collab-plugins/rocksdb_plugin"]

View File

@ -2,13 +2,51 @@ use flowy_folder2::entities::icon::UpdateViewIconPayloadPB;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent;
use flowy_folder2::event_map::FolderEvent::*;
use flowy_user::entities::{
AddWorkspaceMemberPB, QueryWorkspacePB, RemoveWorkspaceMemberPB, RepeatedWorkspaceMemberPB,
WorkspaceMemberPB,
};
use flowy_user::errors::FlowyError;
use flowy_user::event_map::UserEvent;
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
// Must sign up/ sign in first
pub async fn add_workspace_member(&self, workspace_id: &str, email: &str) {
EventBuilder::new(self.clone())
.event(UserEvent::AddWorkspaceMember)
.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)
.payload(RemoveWorkspaceMemberPB {
workspace_id: workspace_id.to_string(),
email: email.to_string(),
})
.async_send()
.await;
}
pub async fn get_workspace_members(&self, workspace_id: &str) -> Vec<WorkspaceMemberPB> {
EventBuilder::new(self.clone())
.event(UserEvent::GetWorkspaceMember)
.payload(QueryWorkspacePB {
workspace_id: workspace_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedWorkspaceMemberPB>()
.items
}
pub async fn get_current_workspace(&self) -> WorkspaceSettingPB {
EventBuilder::new(self.clone())
.event(FolderEvent::GetCurrentWorkspace)

View File

@ -0,0 +1,50 @@
use event_integration::EventIntegrationTest;
use crate::util::get_af_cloud_config;
#[tokio::test]
async fn af_cloud_add_workspace_member_test() {
if get_af_cloud_config().is_some() {
let test_1 = EventIntegrationTest::new();
let user_1 = test_1.af_cloud_sign_up().await;
let test_2 = EventIntegrationTest::new();
let user_2 = test_2.af_cloud_sign_up().await;
let members = test_1.get_workspace_members(&user_1.workspace_id).await;
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;
let members = test_1.get_workspace_members(&user_1.workspace_id).await;
assert_eq!(members.len(), 2);
assert_eq!(members[0].email, user_1.email);
assert_eq!(members[1].email, user_2.email);
}
}
#[tokio::test]
async fn af_cloud_delete_workspace_member_test() {
if get_af_cloud_config().is_some() {
let test_1 = EventIntegrationTest::new();
let user_1 = test_1.af_cloud_sign_up().await;
let test_2 = EventIntegrationTest::new();
let user_2 = test_2.af_cloud_sign_up().await;
test_1
.add_workspace_member(&user_1.workspace_id, &user_2.email)
.await;
test_1
.delete_workspace_member(&user_1.workspace_id, &user_2.email)
.await;
let members = test_1.get_workspace_members(&user_1.workspace_id).await;
assert_eq!(members.len(), 1);
assert_eq!(members[0].email, user_1.email);
}
}

View File

@ -1 +1,2 @@
mod test;
mod auth_test;
mod member_test;

View File

@ -9,7 +9,7 @@ edition = "2021"
collab = { version = "0.1.0" }
collab-document = { version = "0.1.0" }
collab-entity = { version = "0.1.0" }
collab-integrate = { workspace = true }
collab-integrate = { workspace = true, features = ["rocksdb_plugin", "snapshot_plugin"] }
flowy-document-deps = { workspace = true }
flowy-storage = { workspace = true }
@ -37,6 +37,7 @@ tokio-stream = { version = "0.1.14", features = ["sync"] }
[dev-dependencies]
tempfile = "3.4.0"
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
collab-integrate = { workspace = true }
[build-dependencies]
flowy-codegen = { path = "../../../shared-lib/flowy-codegen"}

View File

@ -11,6 +11,7 @@ protobuf = { version = "2.28.0" }
bytes = "1.4"
anyhow = "1.0"
thiserror = "1.0"
validator = "0.16.0"
fancy-regex = { version = "0.11.0" }
lib-dispatch = { workspace = true, optional = true }

View File

@ -3,6 +3,7 @@ use std::fmt::Debug;
use protobuf::ProtobufError;
use thiserror::Error;
use validator::{ValidationError, ValidationErrors};
use flowy_derive::ProtoBuf;
@ -132,6 +133,18 @@ impl std::convert::From<protobuf::ProtobufError> for FlowyError {
}
}
impl From<ValidationError> for FlowyError {
fn from(value: ValidationError) -> Self {
FlowyError::new(ErrorCode::InvalidParams, value)
}
}
impl From<ValidationErrors> for FlowyError {
fn from(value: ValidationErrors) -> Self {
FlowyError::new(ErrorCode::InvalidParams, value)
}
}
impl From<anyhow::Error> for FlowyError {
fn from(e: anyhow::Error) -> Self {
e.downcast::<FlowyError>()

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use anyhow::{anyhow, Error};
use client_api::entity::workspace_dto::CreateWorkspaceMember;
use client_api::entity::workspace_dto::{CreateWorkspaceMember, WorkspaceMemberChangeset};
use client_api::entity::{AFRole, AFWorkspace, InsertCollabParams, OAuthProvider};
use collab_entity::CollabObject;
@ -13,7 +13,7 @@ use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
use crate::af_cloud::impls::user::dto::{
af_update_from_update_params, user_profile_from_af_profile,
af_update_from_update_params, from_af_workspace_member, to_af_role, user_profile_from_af_profile,
};
use crate::af_cloud::impls::user::util::encryption_type_from_profile;
use crate::af_cloud::{AFCloudClient, AFServer};
@ -155,6 +155,38 @@ where
})
}
fn update_workspace_member(
&self,
user_email: String,
workspace_id: String,
role: Role,
) -> FutureResult<(), Error> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let changeset = WorkspaceMemberChangeset::new(user_email).with_role(to_af_role(role));
try_get_client?
.update_workspace_member(workspace_id, changeset)
.await?;
Ok(())
})
}
fn get_workspace_members(
&self,
workspace_id: String,
) -> FutureResult<Vec<WorkspaceMember>, Error> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let members = try_get_client?
.get_workspace_members(&workspace_id)
.await?
.into_iter()
.map(from_af_workspace_member)
.collect();
Ok(members)
})
}
fn get_user_awareness_updates(&self, _uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
// TODO(nathan): implement the RESTful API for this
FutureResult::new(async { Ok(vec![]) })

View File

@ -1,9 +1,9 @@
use anyhow::Error;
use client_api::entity::auth_dto::{UpdateUserParams, UserMetaData};
use client_api::entity::AFUserProfile;
use client_api::entity::{AFRole, AFUserProfile, AFWorkspaceMember};
use flowy_user_deps::entities::{
AuthType, UpdateUserProfileParams, UserProfile, USER_METADATA_ICON_URL,
AuthType, Role, UpdateUserProfileParams, UserProfile, WorkspaceMember, USER_METADATA_ICON_URL,
USER_METADATA_OPEN_AI_KEY, USER_METADATA_STABILITY_AI_KEY,
};
@ -63,3 +63,27 @@ pub fn user_profile_from_af_profile(
updated_at: profile.updated_at,
})
}
pub fn to_af_role(role: Role) -> AFRole {
match role {
Role::Owner => AFRole::Owner,
Role::Member => AFRole::Member,
Role::Guest => AFRole::Guest,
}
}
pub fn from_af_role(role: AFRole) -> Role {
match role {
AFRole::Owner => Role::Owner,
AFRole::Member => Role::Member,
AFRole::Guest => Role::Guest,
}
}
pub fn from_af_workspace_member(member: AFWorkspaceMember) -> WorkspaceMember {
WorkspaceMember {
email: member.email,
role: from_af_role(member.role),
name: member.name,
}
}

View File

@ -120,22 +120,6 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
FutureResult::new(async { Ok(vec![]) })
}
fn add_workspace_member(
&self,
_user_email: String,
_workspace_id: String,
) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn remove_workspace_member(
&self,
_user_email: String,
_workspace_id: String,
) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn get_user_awareness_updates(&self, _uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
FutureResult::new(async { Ok(vec![]) })
}

View File

@ -234,22 +234,6 @@ where
Ok(user_workspaces)
})
}
fn add_workspace_member(
&self,
_user_email: String,
_workspace_id: String,
) -> FutureResult<(), Error> {
todo!()
}
fn remove_workspace_member(
&self,
_user_email: String,
_workspace_id: String,
) -> FutureResult<(), Error> {
todo!()
}
fn get_user_awareness_updates(&self, uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
let try_get_postgrest = self.server.try_get_weak_postgrest();
let awareness_id = uid.to_string();

View File

@ -13,7 +13,8 @@ use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
use crate::entities::{
AuthResponse, UpdateUserProfileParams, UserCredentials, UserProfile, UserWorkspace,
AuthResponse, Role, UpdateUserProfileParams, UserCredentials, UserProfile, UserWorkspace,
WorkspaceMember,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -57,6 +58,7 @@ impl Display for UserCloudConfig {
/// Provide the generic interface for the user cloud service
/// The user cloud service is responsible for the user authentication and user profile management
#[allow(unused_variables)]
pub trait UserCloudService: Send + Sync + 'static {
/// Sign up a new account.
/// The type of the params is defined the this trait's implementation.
@ -98,13 +100,33 @@ pub trait UserCloudService: Send + Sync + 'static {
&self,
user_email: String,
workspace_id: String,
) -> FutureResult<(), Error>;
) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn remove_workspace_member(
&self,
user_email: String,
workspace_id: String,
) -> FutureResult<(), Error>;
) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn update_workspace_member(
&self,
user_email: String,
workspace_id: String,
role: Role,
) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn get_workspace_members(
&self,
workspace_id: String,
) -> FutureResult<Vec<WorkspaceMember>, Error> {
FutureResult::new(async { Ok(vec![]) })
}
fn get_user_awareness_updates(&self, uid: i64) -> FutureResult<Vec<Vec<u8>>, Error>;

View File

@ -377,3 +377,16 @@ pub enum UserTokenState {
Refresh { token: String },
Invalid,
}
#[derive(Clone, Debug)]
pub enum Role {
Owner,
Member,
Guest,
}
pub struct WorkspaceMember {
pub email: String,
pub role: Role,
pub name: String,
}

View File

@ -1,8 +1,11 @@
use validator::ValidationError;
pub use auth::*;
pub use realtime::*;
pub use reminder::*;
pub use user_profile::*;
pub use user_setting::*;
pub use workspace_member::*;
pub mod auth;
pub mod date_time;
@ -11,3 +14,11 @@ pub mod realtime;
mod reminder;
mod user_profile;
mod user_setting;
mod workspace_member;
pub fn required_not_empty_str(s: &str) -> Result<(), ValidationError> {
if s.is_empty() {
return Err(ValidationError::new("should not be empty string"));
}
Ok(())
}

View File

@ -1,3 +1,5 @@
use validator::ValidationError;
pub use user_email::*;
pub use user_icon::*;
pub use user_id::*;
@ -14,3 +16,10 @@ mod user_name;
mod user_openai_key;
mod user_password;
mod user_stability_ai_key;
pub fn validate_not_empty_str(s: &str) -> Result<(), ValidationError> {
if s.is_empty() {
return Err(ValidationError::new("should not be empty string"));
}
Ok(())
}

View File

@ -235,24 +235,6 @@ impl From<UserWorkspace> for UserWorkspacePB {
}
}
#[derive(ProtoBuf, Default)]
pub struct AddWorkspaceUserPB {
#[pb(index = 1)]
pub email: String,
#[pb(index = 2)]
pub workspace_id: String,
}
#[derive(ProtoBuf, Default)]
pub struct RemoveWorkspaceUserPB {
#[pb(index = 1)]
pub email: String,
#[pb(index = 2)]
pub workspace_id: String,
}
#[derive(ProtoBuf, Default, Clone)]
pub struct RepeatedHistoricalUserPB {
#[pb(index = 1)]

View File

@ -0,0 +1,105 @@
use validator::Validate;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_user_deps::entities::{Role, WorkspaceMember};
use crate::entities::required_not_empty_str;
#[derive(ProtoBuf, Default, Clone)]
pub struct WorkspaceMemberPB {
#[pb(index = 1)]
pub email: String,
#[pb(index = 2)]
pub name: String,
#[pb(index = 3)]
pub role: AFRolePB,
}
impl From<WorkspaceMember> for WorkspaceMemberPB {
fn from(value: WorkspaceMember) -> Self {
Self {
email: value.email,
name: value.name,
role: value.role.into(),
}
}
}
#[derive(ProtoBuf, Default, Clone)]
pub struct RepeatedWorkspaceMemberPB {
#[pb(index = 1)]
pub items: Vec<WorkspaceMemberPB>,
}
#[derive(ProtoBuf, Default, Clone, Validate)]
pub struct AddWorkspaceMemberPB {
#[pb(index = 1)]
#[validate(custom = "required_not_empty_str")]
pub workspace_id: String,
#[pb(index = 2)]
#[validate(email)]
pub email: String,
}
#[derive(ProtoBuf, Default, Clone, Validate)]
pub struct QueryWorkspacePB {
#[pb(index = 1)]
#[validate(custom = "required_not_empty_str")]
pub workspace_id: String,
}
#[derive(ProtoBuf, Default, Clone, Validate)]
pub struct RemoveWorkspaceMemberPB {
#[pb(index = 1)]
#[validate(custom = "required_not_empty_str")]
pub workspace_id: String,
#[pb(index = 2)]
#[validate(email)]
pub email: String,
}
#[derive(ProtoBuf, Default, Clone, Validate)]
pub struct UpdateWorkspaceMemberPB {
#[pb(index = 1)]
#[validate(custom = "required_not_empty_str")]
pub workspace_id: String,
#[pb(index = 2)]
#[validate(email)]
pub email: String,
#[pb(index = 3)]
pub role: AFRolePB,
}
#[derive(ProtoBuf_Enum, Clone, Default)]
pub enum AFRolePB {
Owner = 0,
Member = 1,
#[default]
Guest = 2,
}
impl From<AFRolePB> for Role {
fn from(value: AFRolePB) -> Self {
match value {
AFRolePB::Owner => Role::Owner,
AFRolePB::Member => Role::Member,
AFRolePB::Guest => Role::Guest,
}
}
}
impl From<Role> for AFRolePB {
fn from(value: Role) -> Self {
match value {
Role::Owner => AFRolePB::Owner,
Role::Member => AFRolePB::Member,
Role::Guest => AFRolePB::Guest,
}
}
}

View File

@ -445,32 +445,6 @@ pub async fn open_workspace_handler(
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub async fn add_user_to_workspace_handler(
data: AFPluginData<AddWorkspaceUserPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?;
let params = data.into_inner();
manager
.add_user_to_workspace(params.email, params.workspace_id)
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub async fn remove_user_from_workspace_handler(
data: AFPluginData<RemoveWorkspaceUserPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?;
let params = data.into_inner();
manager
.remove_user_to_workspace(params.email, params.workspace_id)
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub async fn update_network_state_handler(
data: AFPluginData<NetworkStatePB>,
@ -591,3 +565,58 @@ pub async fn update_reminder_event_handler(
manager.update_reminder(params).await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn add_workspace_member_handler(
data: AFPluginData<AddWorkspaceMemberPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> {
let data = data.validate()?.into_inner();
let manager = upgrade_manager(manager)?;
manager
.add_workspace_member(data.email, data.workspace_id)
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn delete_workspace_member_handler(
data: AFPluginData<RemoveWorkspaceMemberPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> {
let data = data.validate()?.into_inner();
let manager = upgrade_manager(manager)?;
manager
.remove_workspace_member(data.email, data.workspace_id)
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn get_workspace_member_handler(
data: AFPluginData<QueryWorkspacePB>,
manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<RepeatedWorkspaceMemberPB, FlowyError> {
let data = data.validate()?.into_inner();
let manager = upgrade_manager(manager)?;
let members = manager
.get_workspace_members(data.workspace_id)
.await?
.into_iter()
.map(WorkspaceMemberPB::from)
.collect();
data_result_ok(RepeatedWorkspaceMemberPB { items: members })
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn update_workspace_member_handler(
data: AFPluginData<UpdateWorkspaceMemberPB>,
manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> {
let data = data.validate()?.into_inner();
let manager = upgrade_manager(manager)?;
manager
.update_workspace_member(data.email, data.workspace_id, data.role.into())
.await?;
Ok(())
}

View File

@ -15,6 +15,7 @@ use crate::errors::FlowyError;
use crate::event_handler::*;
use crate::manager::UserManager;
#[rustfmt::skip]
pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
let store_preferences = user_session
.upgrade()
@ -39,20 +40,9 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::CheckEncryptionSign, check_encrypt_secret_handler)
.event(UserEvent::OauthSignIn, oauth_handler)
.event(UserEvent::GetSignInURL, get_sign_in_url_handler)
.event(
UserEvent::GetOauthURLWithProvider,
sign_in_with_provider_handler,
)
.event(
UserEvent::GetAllUserWorkspaces,
get_all_user_workspace_handler,
)
.event(UserEvent::GetOauthURLWithProvider, sign_in_with_provider_handler)
.event(UserEvent::GetAllUserWorkspaces, get_all_user_workspace_handler)
.event(UserEvent::OpenWorkspace, open_workspace_handler)
.event(UserEvent::AddUserToWorkspace, add_user_to_workspace_handler)
.event(
UserEvent::RemoveUserToWorkspace,
remove_user_from_workspace_handler,
)
.event(UserEvent::UpdateNetworkState, update_network_state_handler)
.event(UserEvent::GetHistoricalUsers, get_historical_users_handler)
.event(UserEvent::OpenHistoricalUser, open_historical_users_handler)
@ -64,14 +54,142 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::ResetWorkspace, reset_workspace_handler)
.event(UserEvent::SetDateTimeSettings, set_date_time_settings)
.event(UserEvent::GetDateTimeSettings, get_date_time_settings)
.event(
UserEvent::SetNotificationSettings,
set_notification_settings,
)
.event(
UserEvent::GetNotificationSettings,
get_notification_settings,
)
.event(UserEvent::SetNotificationSettings, set_notification_settings)
.event(UserEvent::GetNotificationSettings, get_notification_settings)
// Workspace member
.event(UserEvent::AddWorkspaceMember, add_workspace_member_handler)
.event(UserEvent::RemoveWorkspaceMember, delete_workspace_member_handler)
.event(UserEvent::GetWorkspaceMember, get_workspace_member_handler)
.event(UserEvent::UpdateWorkspaceMember, update_workspace_member_handler,)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
#[event_err = "FlowyError"]
pub enum UserEvent {
/// Only use when the [AuthType] is Local or SelfHosted
/// Logging into an account using a register email and password
#[event(input = "SignInPayloadPB", output = "UserProfilePB")]
SignIn = 0,
/// Only use when the [AuthType] is Local or SelfHosted
/// Creating a new account
#[event(input = "SignUpPayloadPB", output = "UserProfilePB")]
SignUp = 1,
/// Logging out fo an account
#[event()]
SignOut = 2,
/// Update the user information
#[event(input = "UpdateUserProfilePayloadPB")]
UpdateUserProfile = 3,
/// Get the user information
#[event(output = "UserProfilePB")]
GetUserProfile = 4,
/// Initialize resources for the current user after launching the application
///
#[event()]
InitUser = 6,
/// Change the visual elements of the interface, such as theme, font and more
#[event(input = "AppearanceSettingsPB")]
SetAppearanceSetting = 7,
/// Get the appearance setting
#[event(output = "AppearanceSettingsPB")]
GetAppearanceSetting = 8,
/// Get the settings of the user, such as the user storage folder
#[event(output = "UserSettingPB")]
GetUserSetting = 9,
#[event(input = "OauthSignInPB", output = "UserProfilePB")]
OauthSignIn = 10,
/// Get the OAuth callback url
/// Only use when the [AuthType] is AFCloud
#[event(input = "SignInUrlPayloadPB", output = "SignInUrlPB")]
GetSignInURL = 11,
#[event(input = "OauthProviderPB", output = "OauthProviderDataPB")]
GetOauthURLWithProvider = 12,
#[event(input = "UpdateCloudConfigPB")]
SetCloudConfig = 13,
#[event(output = "UserCloudConfigPB")]
GetCloudConfig = 14,
#[event(input = "UserSecretPB")]
SetEncryptionSecret = 15,
#[event(output = "UserEncryptionConfigurationPB")]
CheckEncryptionSign = 16,
/// Return the all the workspaces of the user
#[event()]
GetAllUserWorkspaces = 20,
#[event(input = "UserWorkspacePB")]
OpenWorkspace = 21,
#[event(input = "NetworkStatePB")]
UpdateNetworkState = 24,
#[event(output = "RepeatedHistoricalUserPB")]
GetHistoricalUsers = 25,
#[event(input = "HistoricalUserPB")]
OpenHistoricalUser = 26,
/// Push a realtime event to the user. Currently, the realtime event
/// is only used when the auth type is: [AuthType::Supabase].
///
#[event(input = "RealtimePayloadPB")]
PushRealtimeEvent = 27,
#[event(input = "ReminderPB")]
CreateReminder = 28,
#[event(output = "RepeatedReminderPB")]
GetAllReminders = 29,
#[event(input = "ReminderIdentifierPB")]
RemoveReminder = 30,
#[event(input = "ReminderPB")]
UpdateReminder = 31,
#[event(input = "ResetWorkspacePB")]
ResetWorkspace = 32,
/// Change the Date/Time formats globally
#[event(input = "DateTimeSettingsPB")]
SetDateTimeSettings = 33,
/// Retrieve the Date/Time formats
#[event(output = "DateTimeSettingsPB")]
GetDateTimeSettings = 34,
#[event(input = "NotificationSettingsPB")]
SetNotificationSettings = 35,
#[event(output = "NotificationSettingsPB")]
GetNotificationSettings = 36,
#[event(output = "AddWorkspaceMemberPB")]
AddWorkspaceMember = 37,
#[event(output = "RemoveWorkspaceMemberPB")]
RemoveWorkspaceMember = 38,
#[event(output = "UpdateWorkspaceMemberPB")]
UpdateWorkspaceMember = 39,
#[event(output = "QueryWorkspacePB")]
GetWorkspaceMember = 40,
}
pub struct SignUpContext {
@ -205,126 +323,3 @@ impl UserStatusCallback for DefaultUserStatusCallback {
to_fut(async { Ok(()) })
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
#[event_err = "FlowyError"]
pub enum UserEvent {
/// Only use when the [AuthType] is Local or SelfHosted
/// Logging into an account using a register email and password
#[event(input = "SignInPayloadPB", output = "UserProfilePB")]
SignIn = 0,
/// Only use when the [AuthType] is Local or SelfHosted
/// Creating a new account
#[event(input = "SignUpPayloadPB", output = "UserProfilePB")]
SignUp = 1,
/// Logging out fo an account
#[event()]
SignOut = 2,
/// Update the user information
#[event(input = "UpdateUserProfilePayloadPB")]
UpdateUserProfile = 3,
/// Get the user information
#[event(output = "UserProfilePB")]
GetUserProfile = 4,
/// Initialize resources for the current user after launching the application
///
#[event()]
InitUser = 6,
/// Change the visual elements of the interface, such as theme, font and more
#[event(input = "AppearanceSettingsPB")]
SetAppearanceSetting = 7,
/// Get the appearance setting
#[event(output = "AppearanceSettingsPB")]
GetAppearanceSetting = 8,
/// Get the settings of the user, such as the user storage folder
#[event(output = "UserSettingPB")]
GetUserSetting = 9,
#[event(input = "OauthSignInPB", output = "UserProfilePB")]
OauthSignIn = 10,
/// Get the OAuth callback url
/// Only use when the [AuthType] is AFCloud
#[event(input = "SignInUrlPayloadPB", output = "SignInUrlPB")]
GetSignInURL = 11,
#[event(input = "OauthProviderPB", output = "OauthProviderDataPB")]
GetOauthURLWithProvider = 12,
#[event(input = "UpdateCloudConfigPB")]
SetCloudConfig = 13,
#[event(output = "UserCloudConfigPB")]
GetCloudConfig = 14,
#[event(input = "UserSecretPB")]
SetEncryptionSecret = 15,
#[event(output = "UserEncryptionConfigurationPB")]
CheckEncryptionSign = 16,
/// Return the all the workspaces of the user
#[event()]
GetAllUserWorkspaces = 20,
#[event(input = "UserWorkspacePB")]
OpenWorkspace = 21,
#[event(input = "AddWorkspaceUserPB")]
AddUserToWorkspace = 22,
#[event(input = "RemoveWorkspaceUserPB")]
RemoveUserToWorkspace = 23,
#[event(input = "NetworkStatePB")]
UpdateNetworkState = 24,
#[event(output = "RepeatedHistoricalUserPB")]
GetHistoricalUsers = 25,
#[event(input = "HistoricalUserPB")]
OpenHistoricalUser = 26,
/// Push a realtime event to the user. Currently, the realtime event
/// is only used when the auth type is: [AuthType::Supabase].
///
#[event(input = "RealtimePayloadPB")]
PushRealtimeEvent = 27,
#[event(input = "ReminderPB")]
CreateReminder = 28,
#[event(output = "RepeatedReminderPB")]
GetAllReminders = 29,
#[event(input = "ReminderIdentifierPB")]
RemoveReminder = 30,
#[event(input = "ReminderPB")]
UpdateReminder = 31,
#[event(input = "ResetWorkspacePB")]
ResetWorkspace = 32,
/// Change the Date/Time formats globally
#[event(input = "DateTimeSettingsPB")]
SetDateTimeSettings = 33,
/// Retrieve the Date/Time formats
#[event(output = "DateTimeSettingsPB")]
GetDateTimeSettings = 34,
#[event(input = "NotificationSettingsPB")]
SetNotificationSettings = 35,
#[event(output = "NotificationSettingsPB")]
GetNotificationSettings = 36,
}

View File

@ -6,7 +6,7 @@ use collab_entity::{CollabObject, CollabType};
use flowy_error::{FlowyError, FlowyResult};
use flowy_sqlite::schema::user_workspace_table;
use flowy_sqlite::{query_dsl::*, ConnectionPool, ExpressionMethods};
use flowy_user_deps::entities::UserWorkspace;
use flowy_user_deps::entities::{Role, UserWorkspace, WorkspaceMember};
use crate::entities::{RepeatedUserWorkspacePB, ResetWorkspacePB};
use crate::manager::UserManager;
@ -30,28 +30,54 @@ impl UserManager {
Ok(())
}
pub async fn add_user_to_workspace(
pub async fn add_workspace_member(
&self,
user_email: String,
to_workspace_id: String,
workspace_id: String,
) -> FlowyResult<()> {
self
.cloud_services
.get_user_service()?
.add_workspace_member(user_email, to_workspace_id)
.add_workspace_member(user_email, workspace_id)
.await?;
Ok(())
}
pub async fn remove_user_to_workspace(
pub async fn remove_workspace_member(
&self,
user_email: String,
from_workspace_id: String,
workspace_id: String,
) -> FlowyResult<()> {
self
.cloud_services
.get_user_service()?
.remove_workspace_member(user_email, from_workspace_id)
.remove_workspace_member(user_email, workspace_id)
.await?;
Ok(())
}
pub async fn get_workspace_members(
&self,
workspace_id: String,
) -> FlowyResult<Vec<WorkspaceMember>> {
let members = self
.cloud_services
.get_user_service()?
.get_workspace_members(workspace_id)
.await?;
Ok(members)
}
pub async fn update_workspace_member(
&self,
user_email: String,
workspace_id: String,
role: Role,
) -> FlowyResult<()> {
self
.cloud_services
.get_user_service()?
.update_workspace_member(user_email, workspace_id, role)
.await?;
Ok(())
}

View File

@ -21,6 +21,7 @@ derivative = "2.2.0"
serde_json = {version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_repr = { version = "0.1", optional = true }
validator = "0.16.1"
#optional crate
bincode = { version = "1.3", optional = true}

View File

@ -2,6 +2,7 @@ use std::fmt::{Debug, Formatter};
use std::ops;
use bytes::Bytes;
use validator::ValidationErrors;
use crate::{
byte_trait::*,
@ -11,6 +12,12 @@ use crate::{
util::ready::{ready, Ready},
};
pub trait AFPluginDataValidator {
fn validate(self) -> Result<Self, ValidationErrors>
where
Self: Sized;
}
pub struct AFPluginData<T>(pub T);
impl<T> AFPluginData<T> {
@ -27,6 +34,16 @@ impl<T> ops::Deref for AFPluginData<T> {
}
}
impl<T> AFPluginDataValidator for AFPluginData<T>
where
T: validator::Validate,
{
fn validate(self) -> Result<Self, ValidationErrors> {
self.0.validate()?;
Ok(self)
}
}
impl<T> ops::DerefMut for AFPluginData<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0