feat: save user metadata (#3754)

* feat: save user metadata

* chore: update client api

* refactor: separate test methods

* chore: save updated at

* chore: clippy

* chore: fix test
This commit is contained in:
Nathan.fooo 2023-10-24 20:11:06 +08:00 committed by GitHub
parent 16a4babdca
commit 71f80be8f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1525 additions and 1476 deletions

View File

@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ProtoBuf_Gen" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --manifest-path ${flowy_tool} -- pb-gen --rust_sources=${rust_lib},${shared_lib} --derive_meta=${derive_meta} --flutter_package_lib=${flutter_package_lib}" />
<option name="workingDirectory" value="file://$PROJECT_DIR$/frontend" />
<option name="channel" value="DEFAULT" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<envs>
<env name="flowy_tool" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/scripts/flowy-tool/Cargo.toml" />
<env name="rust_lib" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/" />
<env name="shared_lib" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/../shared_lib" />
<env name="flutter_lib" value="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/appflowy_flutter/packages" />
<env name="derive_meta" value="${shared_lib}/flowy-derive/src/derive_cache/derive_cache.rs" />
<env name="flutter_package_lib" value="${flutter_lib}/flowy_sdk/lib" />
</envs>
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run backend" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --package backend --bin backend" />
<option name="workingDirectory" value="file://$PROJECT_DIR$/backend" />
<option name="channel" value="DEFAULT" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<envs>
<env name="APP_ENVIRONMENT" value="production" />
</envs>
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,16 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="dart-event" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run --manifest-path $PROJECT_DIR$/scripts/flowy-tool/Cargo.toml -- dart-event --rust_source=$PROJECT_DIR$/rust-lib/ --output=$PROJECT_DIR$/appflowy_flutter/packages/flowy_sdk/lib/dispatch/dart_event.dart" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<envs />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -42,10 +42,7 @@ class PluginSandbox {
PluginConfig? config,
}) {
if (_pluginBuilders.containsKey(pluginType)) {
throw PlatformException(
code: '-1',
message: "$pluginType was registered before",
);
return;
}
_pluginBuilders[pluginType] = builder;

View File

@ -2,6 +2,7 @@ import 'package:appflowy/env/env.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/user_auth_listener.dart';
import 'package:appflowy_backend/log.dart';
class InitAppFlowyCloudTask extends LaunchTask {
final _authStateListener = UserAuthStateListener();
@ -18,6 +19,7 @@ class InitAppFlowyCloudTask extends LaunchTask {
isLoggingOut = false;
},
onInvalidAuth: (message) async {
Log.error(message);
await getIt<AuthService>().signOut();
// TODO(nathan): Show a dialog to notify the user that the session is expired.
if (!isLoggingOut) {

View File

@ -53,7 +53,7 @@ class UserAuthStateListener {
_didSignIn?.call();
break;
case AuthStatePB.InvalidAuth:
_onInvalidAuth?.call("");
_onInvalidAuth?.call(pb.message);
break;
default:
break;

View File

@ -14,16 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
// [[diagram: splash screen]]
// 1.get user 2.send UserEventCheckUser
// SplashScreen SplashBlocISplashUser
//
//
//
//
// HomeScreen BlocListener RustSDK
//
// 4. Show HomeScreen or SignIn 3.return AuthState
class SplashScreen extends StatelessWidget {
/// Root Page of the app.
const SplashScreen({
@ -82,7 +72,7 @@ class SplashScreen extends StatelessWidget {
(check) async {
/// If encryption is needed, the user is navigated to the encryption screen.
/// Otherwise, it fetches the current workspace for the user and navigates them
if (check.isNeedSecret) {
if (check.requireSecret) {
getIt<AuthRouter>().pushEncryptionScreen(context, userProfile);
} else {
final result = await FolderEventGetCurrentWorkspace().send();

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=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"bytes",
@ -1438,7 +1438,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"chrono",
@ -2781,7 +2781,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"futures-util",
@ -2797,7 +2797,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"jsonwebtoken",
@ -3232,7 +3232,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"reqwest",
@ -4915,7 +4915,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"bytes",
"collab",
@ -5637,9 +5637,10 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"collab-entity",
"database-entity",
"gotrue-entity",
"opener",

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 = "7a309c6f69d8b34709292052e9ef0561e16c82a1" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "141354bfa9d08c387cf9beb9697b89b052790e89" }
# 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=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"bytes",
@ -1138,7 +1138,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.8.0",
"phf 0.11.2",
"smallvec",
]
@ -1265,7 +1265,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"chrono",
@ -2440,7 +2440,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"futures-util",
@ -2456,7 +2456,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"jsonwebtoken",
@ -2816,7 +2816,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"reqwest",
@ -3623,7 +3623,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_macros 0.8.0",
"phf_shared 0.8.0",
"proc-macro-hack",
]
@ -3643,6 +3643,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",
]
@ -3710,6 +3711,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.31",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
@ -4251,7 +4265,7 @@ dependencies = [
[[package]]
name = "realtime-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"bytes",
"collab",
@ -4872,9 +4886,10 @@ dependencies = [
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7a309c6f69d8b34709292052e9ef0561e16c82a1#7a309c6f69d8b34709292052e9ef0561e16c82a1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=141354bfa9d08c387cf9beb9697b89b052790e89#141354bfa9d08c387cf9beb9697b89b052790e89"
dependencies = [
"anyhow",
"collab-entity",
"database-entity",
"gotrue-entity",
"opener",

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 = "7a309c6f69d8b34709292052e9ef0561e16c82a1" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "141354bfa9d08c387cf9beb9697b89b052790e89" }
# Please use the following script to update collab.
# Working directory: frontend
#

View File

@ -1,18 +1,20 @@
use std::collections::HashMap;
use serde_json::Value;
use flowy_document2::entities::*;
use flowy_document2::event_map::DocumentEvent;
use flowy_folder2::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
use flowy_folder2::event_map::FolderEvent;
use serde_json::Value;
use std::collections::HashMap;
use crate::document::utils::{gen_delta_str, gen_id, gen_text_block_data};
use crate::event_builder::EventBuilder;
use crate::FlowyCoreTest;
use crate::EventIntegrationTest;
const TEXT_BLOCK_TY: &str = "paragraph";
pub struct DocumentEventTest {
inner: FlowyCoreTest,
inner: EventIntegrationTest,
}
pub struct OpenDocumentData {
@ -22,11 +24,11 @@ pub struct OpenDocumentData {
impl DocumentEventTest {
pub async fn new() -> Self {
let sdk = FlowyCoreTest::new_with_guest_user().await;
let sdk = EventIntegrationTest::new_with_guest_user().await;
Self { inner: sdk }
}
pub fn new_with_core(core: FlowyCoreTest) -> Self {
pub fn new_with_core(core: EventIntegrationTest) -> Self {
Self { inner: core }
}

View File

@ -10,7 +10,7 @@ use lib_dispatch::prelude::{
AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, ToBytes, *,
};
use crate::FlowyCoreTest;
use crate::EventIntegrationTest;
#[derive(Clone)]
pub struct EventBuilder {
@ -18,7 +18,7 @@ pub struct EventBuilder {
}
impl EventBuilder {
pub fn new(sdk: FlowyCoreTest) -> Self {
pub fn new(sdk: EventIntegrationTest) -> Self {
Self {
context: TestContext::new(sdk),
}
@ -121,13 +121,13 @@ impl EventBuilder {
#[derive(Clone)]
pub struct TestContext {
pub sdk: FlowyCoreTest,
pub sdk: EventIntegrationTest,
request: Option<AFPluginRequest>,
response: Option<AFPluginEventResponse>,
}
impl TestContext {
pub fn new(sdk: FlowyCoreTest) -> Self {
pub fn new(sdk: EventIntegrationTest) -> Self {
Self {
sdk,
request: None,

View File

@ -1,115 +0,0 @@
use crate::event_builder::EventBuilder;
use crate::FlowyCoreTest;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent::*;
pub struct ViewTest {
pub sdk: FlowyCoreTest,
pub workspace: WorkspacePB,
pub parent_view: ViewPB,
pub child_view: ViewPB,
}
impl ViewTest {
#[allow(dead_code)]
pub async fn new(sdk: &FlowyCoreTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
let workspace = create_workspace(sdk, "Workspace", "").await;
open_workspace(sdk, &workspace.id).await;
let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await;
let view = create_view(sdk, &app.id, layout, data).await;
Self {
sdk: sdk.clone(),
workspace,
parent_view: app,
child_view: view,
}
}
pub async fn new_grid_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Grid, data).await
}
pub async fn new_board_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Board, data).await
}
pub async fn new_calendar_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Calendar, data).await
}
pub async fn new_document_view(sdk: &FlowyCoreTest) -> Self {
Self::new(sdk, ViewLayoutPB::Document, vec![]).await
}
}
async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
let request = CreateWorkspacePayloadPB {
name: name.to_owned(),
desc: desc.to_owned(),
};
EventBuilder::new(sdk.clone())
.event(CreateWorkspace)
.payload(request)
.async_send()
.await
.parse::<WorkspacePB>()
}
async fn open_workspace(sdk: &FlowyCoreTest, workspace_id: &str) {
let payload = WorkspaceIdPB {
value: Some(workspace_id.to_owned()),
};
let _ = EventBuilder::new(sdk.clone())
.event(OpenWorkspace)
.payload(payload)
.async_send()
.await;
}
async fn create_app(sdk: &FlowyCoreTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
let create_app_request = CreateViewPayloadPB {
parent_view_id: workspace_id.to_owned(),
name: name.to_string(),
desc: desc.to_string(),
thumbnail: None,
layout: ViewLayoutPB::Document,
initial_data: vec![],
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(create_app_request)
.async_send()
.await
.parse::<ViewPB>()
}
async fn create_view(
sdk: &FlowyCoreTest,
app_id: &str,
layout: ViewLayoutPB,
data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: app_id.to_string(),
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
layout,
initial_data: data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}

View File

@ -1,59 +1,33 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::env::temp_dir;
use std::path::PathBuf;
use std::sync::Arc;
use bytes::Bytes;
use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab::preclude::updates::decoder::Decode;
use collab::preclude::{merge_updates_v1, Update};
use collab_document::blocks::DocumentData;
use collab_document::document::Document;
use nanoid::nanoid;
use parking_lot::RwLock;
use protobuf::ProtobufError;
use tokio::sync::broadcast::{channel, Sender};
use uuid::Uuid;
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
use flowy_database2::entities::*;
use flowy_database2::event_map::DatabaseEvent;
use flowy_document2::entities::{DocumentDataPB, OpenDocumentPayloadPB};
use flowy_document2::event_map::DocumentEvent;
use flowy_folder2::entities::icon::UpdateViewIconPayloadPB;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent;
use flowy_notification::entities::SubscribeObject;
use flowy_notification::{register_notification_sender, NotificationSender};
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID};
use flowy_user::entities::{
AuthTypePB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, UpdateCloudConfigPB,
UserCloudConfigPB, UserProfilePB,
};
use flowy_user::errors::{FlowyError, FlowyResult};
use flowy_user::event_map::UserEvent::*;
use flowy_notification::register_notification_sender;
use flowy_user::entities::AuthTypePB;
use crate::document::document_event::{DocumentEventTest, OpenDocumentData};
use crate::event_builder::EventBuilder;
use crate::user_event::{async_sign_up, SignUpContext};
use crate::test_user::TestNotificationSender;
pub mod document;
pub mod event_builder;
pub mod folder_event;
pub mod user_event;
pub mod test_database;
pub mod test_document;
pub mod test_folder;
pub mod test_user;
#[derive(Clone)]
pub struct FlowyCoreTest {
auth_type: Arc<RwLock<AuthTypePB>>,
inner: AppFlowyCore,
pub struct EventIntegrationTest {
pub auth_type: Arc<RwLock<AuthTypePB>>,
pub inner: AppFlowyCore,
#[allow(dead_code)]
cleaner: Arc<Cleaner>,
pub notification_sender: TestNotificationSender,
}
impl Default for FlowyCoreTest {
impl Default for EventIntegrationTest {
fn default() -> Self {
let temp_dir = temp_dir().join(nanoid!(6));
std::fs::create_dir_all(&temp_dir).unwrap();
@ -61,51 +35,10 @@ impl Default for FlowyCoreTest {
}
}
impl FlowyCoreTest {
impl EventIntegrationTest {
pub fn new() -> Self {
Self::default()
}
pub async fn insert_document_text(&self, document_id: &str, text: &str, index: usize) {
let document_event = DocumentEventTest::new_with_core(self.clone());
document_event
.insert_index(document_id, text, index, None)
.await;
}
pub async fn get_document_data(&self, view_id: &str) -> DocumentData {
let pb = EventBuilder::new(self.clone())
.event(DocumentEvent::GetDocumentData)
.payload(OpenDocumentPayloadPB {
document_id: view_id.to_string(),
})
.async_send()
.await
.parse::<DocumentDataPB>();
DocumentData::from(pb)
}
pub async fn get_document_update(&self, document_id: &str) -> Vec<u8> {
let workspace_id = self.user_manager.workspace_id().unwrap();
let cloud_service = self.document_manager.get_cloud_service().clone();
let remote_updates = cloud_service
.get_document_updates(document_id, &workspace_id)
.await
.unwrap();
if remote_updates.is_empty() {
return vec![];
}
let updates = remote_updates
.iter()
.map(|update| update.as_ref())
.collect::<Vec<&[u8]>>();
merge_updates_v1(&updates).unwrap()
}
pub fn new_with_user_data_path(path: PathBuf, name: String) -> Self {
let config = AppFlowyCoreConfig::new(path.to_str().unwrap(), name).log_filter(
"trace",
@ -129,711 +62,9 @@ impl FlowyCoreTest {
cleaner: Arc::new(Cleaner(path)),
}
}
pub async fn enable_encryption(&self) -> String {
let config = EventBuilder::new(self.clone())
.event(GetCloudConfig)
.async_send()
.await
.parse::<UserCloudConfigPB>();
let update = UpdateCloudConfigPB {
enable_sync: None,
enable_encrypt: Some(true),
};
let error = EventBuilder::new(self.clone())
.event(SetCloudConfig)
.payload(update)
.async_send()
.await
.error();
assert!(error.is_none());
config.encrypt_secret
}
pub async fn get_user_profile(&self) -> Result<UserProfilePB, FlowyError> {
EventBuilder::new(self.clone())
.event(GetUserProfile)
.async_send()
.await
.try_parse::<UserProfilePB>()
}
pub async fn new_with_guest_user() -> Self {
let test = Self::default();
test.sign_up_as_guest().await;
test
}
pub async fn sign_up_as_guest(&self) -> SignUpContext {
async_sign_up(self.inner.dispatcher(), AuthTypePB::Local).await
}
pub async fn supabase_party_sign_up(&self) -> UserProfilePB {
let map = third_party_sign_up_param(Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.parse::<UserProfilePB>()
}
pub async fn sign_out(&self) {
EventBuilder::new(self.clone())
.event(SignOut)
.async_send()
.await;
}
pub fn set_auth_type(&self, auth_type: AuthTypePB) {
*self.auth_type.write() = auth_type;
}
pub async fn init_user(&self) -> UserProfilePB {
self.sign_up_as_guest().await.user_profile
}
pub async fn af_cloud_sign_in_with_email(&self, email: &str) -> FlowyResult<UserProfilePB> {
let payload = SignInUrlPayloadPB {
email: email.to_string(),
auth_type: AuthTypePB::AFCloud,
};
let sign_in_url = EventBuilder::new(self.clone())
.event(GetSignInURL)
.payload(payload)
.async_send()
.await
.try_parse::<SignInUrlPB>()?
.sign_in_url;
let mut map = HashMap::new();
map.insert(USER_SIGN_IN_URL.to_string(), sign_in_url);
map.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::AFCloud,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
pub async fn supabase_sign_up_with_uuid(
&self,
uuid: &str,
email: Option<String>,
) -> FlowyResult<UserProfilePB> {
let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid.to_string());
map.insert(USER_DEVICE_ID.to_string(), uuid.to_string());
map.insert(
USER_EMAIL.to_string(),
email.unwrap_or_else(|| format!("{}@appflowy.io", nanoid!(10))),
);
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
// Must sign up/ sign in first
pub async fn get_current_workspace(&self) -> WorkspaceSettingPB {
EventBuilder::new(self.clone())
.event(FolderEvent::GetCurrentWorkspace)
.async_send()
.await
.parse::<WorkspaceSettingPB>()
}
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadWorkspaceViews)
.async_send()
.await
.parse::<RepeatedViewPB>()
.items
}
pub async fn get_views(&self, parent_view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: parent_view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn delete_view(&self, view_id: &str) {
let payload = RepeatedViewIdPB {
items: vec![view_id.to_string()],
};
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::DeleteView)
.payload(payload)
.async_send()
.await;
}
pub async fn update_view(&self, changeset: UpdateViewPayloadPB) -> Option<FlowyError> {
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateView)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_view_icon(&self, payload: UpdateViewIconPayloadPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateViewIcon)
.payload(payload)
.async_send()
.await
.error()
}
pub async fn create_view(&self, parent_id: &str, name: String) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: Default::default(),
initial_data: vec![],
meta: Default::default(),
set_as_current: false,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn create_document(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Document,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
let view = EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>();
let payload = OpenDocumentPayloadPB {
document_id: view.id.clone(),
};
let _ = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
view
}
pub async fn create_grid(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Grid,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn open_database(&self, view_id: &str) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await;
}
pub async fn open_document(&self, doc_id: String) -> OpenDocumentData {
let payload = OpenDocumentPayloadPB {
document_id: doc_id.clone(),
};
let data = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
OpenDocumentData { id: doc_id, data }
}
pub async fn create_board(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Board,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn create_calendar(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Calendar,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn get_database(&self, view_id: &str) -> DatabasePB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<DatabasePB>()
}
pub async fn get_all_database_fields(&self, view_id: &str) -> RepeatedFieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetFields)
.payload(GetFieldPayloadPB {
view_id: view_id.to_string(),
field_ids: None,
})
.async_send()
.await
.parse::<RepeatedFieldPB>()
}
pub async fn create_field(&self, view_id: &str, field_type: FieldType) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateTypeOption)
.payload(CreateFieldPayloadPB {
view_id: view_id.to_string(),
field_type,
type_option_data: None,
})
.async_send()
.await
.parse::<TypeOptionPB>()
.field
}
pub async fn update_field(&self, changeset: FieldChangesetPB) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateField)
.payload(changeset)
.async_send()
.await;
}
pub async fn delete_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteField)
.payload(DeleteFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_field_type(
&self,
view_id: &str,
field_id: &str,
field_type: FieldType,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateFieldType)
.payload(UpdateFieldTypePayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
field_type,
})
.async_send()
.await
.error()
}
pub async fn duplicate_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateField)
.payload(DuplicateFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn get_primary_field(&self, database_view_id: &str) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetPrimaryField)
.payload(DatabaseViewIdPB {
value: database_view_id.to_string(),
})
.async_send()
.await
.parse::<FieldPB>()
}
pub async fn create_row(
&self,
view_id: &str,
start_row_id: Option<String>,
data: Option<RowDataPB>,
) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateRow)
.payload(CreateRowPayloadPB {
view_id: view_id.to_string(),
start_row_id,
group_id: None,
data,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn delete_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn get_row(&self, view_id: &str, row_id: &str) -> OptionalRowPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<OptionalRowPB>()
}
pub async fn get_row_meta(&self, view_id: &str, row_id: &str) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRowMeta)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn update_row_meta(&self, changeset: UpdateRowMetaChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateRowMeta)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn move_row(&self, view_id: &str, row_id: &str, to_row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveRow)
.payload(MoveRowPayloadPB {
view_id: view_id.to_string(),
from_row_id: row_id.to_string(),
to_row_id: to_row_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_cell(&self, changeset: CellChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_date_cell(&self, changeset: DateChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> CellPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetCell)
.payload(CellIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.parse::<CellPB>()
}
pub async fn get_date_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> DateCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
DateCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn get_checklist_cell(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
) -> ChecklistCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
ChecklistCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn update_checklist_cell(
&self,
changeset: ChecklistCellDataChangesetPB,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateChecklistCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn insert_option(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
name: &str,
) -> Option<FlowyError> {
let option = EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateSelectOption)
.payload(CreateSelectOptionPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
option_name: name.to_string(),
})
.async_send()
.await
.parse::<SelectOptionPB>();
EventBuilder::new(self.clone())
.event(DatabaseEvent::InsertOrUpdateSelectOption)
.payload(RepeatedSelectOptionPayload {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
row_id: row_id.to_string(),
items: vec![option],
})
.async_send()
.await
.error()
}
pub async fn get_groups(&self, view_id: &str) -> Vec<GroupPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetGroups)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedGroupPB>()
.items
}
pub async fn move_group(&self, view_id: &str, from_id: &str, to_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveGroup)
.payload(MoveGroupPayloadPB {
view_id: view_id.to_string(),
from_group_id: from_id.to_string(),
to_group_id: to_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::SetGroupByField)
.payload(GroupByFieldPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_group(
&self,
view_id: &str,
group_id: &str,
name: Option<String>,
visible: Option<bool>,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateGroup)
.payload(UpdateGroupPB {
view_id: view_id.to_string(),
group_id: group_id.to_string(),
name,
visible,
})
.async_send()
.await
.error()
}
pub async fn update_setting(&self, changeset: DatabaseSettingChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDatabaseSetting)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec<CalendarEventPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetAllCalendarEvents)
.payload(CalendarEventRequestPB {
view_id: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedCalendarEventPB>()
.items
}
pub async fn get_view(&self, view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
}
impl std::ops::Deref for FlowyCoreTest {
impl std::ops::Deref for EventIntegrationTest {
type Target = AppFlowyCore;
fn deref(&self) -> &Self::Target {
@ -841,89 +72,6 @@ impl std::ops::Deref for FlowyCoreTest {
}
}
#[derive(Clone)]
pub struct TestNotificationSender {
sender: Arc<Sender<SubscribeObject>>,
}
impl Default for TestNotificationSender {
fn default() -> Self {
let (sender, _) = channel(1000);
Self {
sender: Arc::new(sender),
}
}
}
impl TestNotificationSender {
pub fn new() -> Self {
Self::default()
}
pub fn subscribe<T>(&self, id: &str, ty: impl Into<i32> + Send) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
let ty = ty.into();
tokio::spawn(async move {
// DatabaseNotification::DidUpdateDatabaseSnapshotState
while let Ok(value) = receiver.recv().await {
if value.id == id && value.ty == ty {
if let Some(payload) = value.payload {
match T::try_from(Bytes::from(payload)) {
Ok(object) => {
let _ = tx.send(object).await;
},
Err(e) => {
panic!(
"Failed to parse notification payload to type: {:?} with error: {}",
std::any::type_name::<T>(),
e
);
},
}
}
}
}
});
rx
}
pub fn subscribe_with_condition<T, F>(&self, id: &str, when: F) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
F: Fn(&T) -> bool + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
tokio::spawn(async move {
while let Ok(value) = receiver.recv().await {
if value.id == id {
if let Some(payload) = value.payload {
if let Ok(object) = T::try_from(Bytes::from(payload)) {
if when(&object) {
let _ = tx.send(object).await;
}
}
}
}
}
});
rx
}
}
impl NotificationSender for TestNotificationSender {
fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> {
let _ = self.sender.send(subject);
Ok(())
}
}
pub struct Cleaner(PathBuf);
impl Cleaner {
@ -941,25 +89,3 @@ impl Drop for Cleaner {
Self::cleanup(&self.0)
}
}
pub fn third_party_sign_up_param(uuid: String) -> HashMap<String, String> {
let mut params = HashMap::new();
params.insert(USER_UUID.to_string(), uuid);
params.insert(
USER_EMAIL.to_string(),
format!("{}@test.com", Uuid::new_v4()),
);
params.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
params
}
pub fn assert_document_data_equal(collab_update: &[u8], doc_id: &str, expected: DocumentData) {
let collab = MutexCollab::new(CollabOrigin::Server, doc_id, vec![]);
collab.lock().with_origin_transact_mut(|txn| {
let update = Update::decode_v1(collab_update).unwrap();
txn.apply_update(update);
});
let document = Document::open(Arc::new(collab)).unwrap();
let actual = document.get_document_data().unwrap();
assert_eq!(actual, expected);
}

View File

@ -0,0 +1,447 @@
use std::convert::TryFrom;
use bytes::Bytes;
use flowy_database2::entities::*;
use flowy_database2::event_map::DatabaseEvent;
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent;
use flowy_user::errors::FlowyError;
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
pub async fn create_grid(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Grid,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn open_database(&self, view_id: &str) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await;
}
pub async fn create_board(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Board,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn create_calendar(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Calendar,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn get_database(&self, view_id: &str) -> DatabasePB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetDatabase)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<DatabasePB>()
}
pub async fn get_all_database_fields(&self, view_id: &str) -> RepeatedFieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetFields)
.payload(GetFieldPayloadPB {
view_id: view_id.to_string(),
field_ids: None,
})
.async_send()
.await
.parse::<RepeatedFieldPB>()
}
pub async fn create_field(&self, view_id: &str, field_type: FieldType) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateTypeOption)
.payload(CreateFieldPayloadPB {
view_id: view_id.to_string(),
field_type,
type_option_data: None,
})
.async_send()
.await
.parse::<TypeOptionPB>()
.field
}
pub async fn update_field(&self, changeset: FieldChangesetPB) {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateField)
.payload(changeset)
.async_send()
.await;
}
pub async fn delete_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteField)
.payload(DeleteFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_field_type(
&self,
view_id: &str,
field_id: &str,
field_type: FieldType,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateFieldType)
.payload(UpdateFieldTypePayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
field_type,
})
.async_send()
.await
.error()
}
pub async fn duplicate_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateField)
.payload(DuplicateFieldPayloadPB {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn get_primary_field(&self, database_view_id: &str) -> FieldPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetPrimaryField)
.payload(DatabaseViewIdPB {
value: database_view_id.to_string(),
})
.async_send()
.await
.parse::<FieldPB>()
}
pub async fn create_row(
&self,
view_id: &str,
start_row_id: Option<String>,
data: Option<RowDataPB>,
) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateRow)
.payload(CreateRowPayloadPB {
view_id: view_id.to_string(),
start_row_id,
group_id: None,
data,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn delete_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DeleteRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn get_row(&self, view_id: &str, row_id: &str) -> OptionalRowPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<OptionalRowPB>()
}
pub async fn get_row_meta(&self, view_id: &str, row_id: &str) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetRowMeta)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.parse::<RowMetaPB>()
}
pub async fn update_row_meta(&self, changeset: UpdateRowMetaChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateRowMeta)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::DuplicateRow)
.payload(RowIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
group_id: None,
})
.async_send()
.await
.error()
}
pub async fn move_row(&self, view_id: &str, row_id: &str, to_row_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveRow)
.payload(MoveRowPayloadPB {
view_id: view_id.to_string(),
from_row_id: row_id.to_string(),
to_row_id: to_row_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_cell(&self, changeset: CellChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_date_cell(&self, changeset: DateChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDateCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> CellPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetCell)
.payload(CellIdPB {
view_id: view_id.to_string(),
row_id: row_id.to_string(),
field_id: field_id.to_string(),
})
.async_send()
.await
.parse::<CellPB>()
}
pub async fn get_date_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> DateCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
DateCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn get_checklist_cell(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
) -> ChecklistCellDataPB {
let cell = self.get_cell(view_id, row_id, field_id).await;
ChecklistCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
}
pub async fn update_checklist_cell(
&self,
changeset: ChecklistCellDataChangesetPB,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateChecklistCell)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn insert_option(
&self,
view_id: &str,
field_id: &str,
row_id: &str,
name: &str,
) -> Option<FlowyError> {
let option = EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateSelectOption)
.payload(CreateSelectOptionPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
option_name: name.to_string(),
})
.async_send()
.await
.parse::<SelectOptionPB>();
EventBuilder::new(self.clone())
.event(DatabaseEvent::InsertOrUpdateSelectOption)
.payload(RepeatedSelectOptionPayload {
view_id: view_id.to_string(),
field_id: field_id.to_string(),
row_id: row_id.to_string(),
items: vec![option],
})
.async_send()
.await
.error()
}
pub async fn get_groups(&self, view_id: &str) -> Vec<GroupPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetGroups)
.payload(DatabaseViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedGroupPB>()
.items
}
pub async fn move_group(&self, view_id: &str, from_id: &str, to_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::MoveGroup)
.payload(MoveGroupPayloadPB {
view_id: view_id.to_string(),
from_group_id: from_id.to_string(),
to_group_id: to_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::SetGroupByField)
.payload(GroupByFieldPayloadPB {
field_id: field_id.to_string(),
view_id: view_id.to_string(),
})
.async_send()
.await
.error()
}
pub async fn update_group(
&self,
view_id: &str,
group_id: &str,
name: Option<String>,
visible: Option<bool>,
) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateGroup)
.payload(UpdateGroupPB {
view_id: view_id.to_string(),
group_id: group_id.to_string(),
name,
visible,
})
.async_send()
.await
.error()
}
pub async fn update_setting(&self, changeset: DatabaseSettingChangesetPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::UpdateDatabaseSetting)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec<CalendarEventPB> {
EventBuilder::new(self.clone())
.event(DatabaseEvent::GetAllCalendarEvents)
.payload(CalendarEventRequestPB {
view_id: view_id.to_string(),
})
.async_send()
.await
.parse::<RepeatedCalendarEventPB>()
.items
}
}

View File

@ -0,0 +1,119 @@
use std::sync::Arc;
use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab::preclude::updates::decoder::Decode;
use collab::preclude::{merge_updates_v1, Update};
use collab_document::blocks::DocumentData;
use collab_document::document::Document;
use flowy_document2::entities::{DocumentDataPB, OpenDocumentPayloadPB};
use flowy_document2::event_map::DocumentEvent;
use flowy_folder2::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
use flowy_folder2::event_map::FolderEvent;
use crate::document::document_event::{DocumentEventTest, OpenDocumentData};
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
pub async fn create_document(
&self,
parent_id: &str,
name: String,
initial_data: Vec<u8>,
) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: ViewLayoutPB::Document,
initial_data,
meta: Default::default(),
set_as_current: true,
index: None,
};
let view = EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>();
let payload = OpenDocumentPayloadPB {
document_id: view.id.clone(),
};
let _ = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
view
}
pub async fn open_document(&self, doc_id: String) -> OpenDocumentData {
let payload = OpenDocumentPayloadPB {
document_id: doc_id.clone(),
};
let data = EventBuilder::new(self.clone())
.event(DocumentEvent::OpenDocument)
.payload(payload)
.async_send()
.await
.parse::<DocumentDataPB>();
OpenDocumentData { id: doc_id, data }
}
pub async fn insert_document_text(&self, document_id: &str, text: &str, index: usize) {
let document_event = DocumentEventTest::new_with_core(self.clone());
document_event
.insert_index(document_id, text, index, None)
.await;
}
pub async fn get_document_data(&self, view_id: &str) -> DocumentData {
let pb = EventBuilder::new(self.clone())
.event(DocumentEvent::GetDocumentData)
.payload(OpenDocumentPayloadPB {
document_id: view_id.to_string(),
})
.async_send()
.await
.parse::<DocumentDataPB>();
DocumentData::from(pb)
}
pub async fn get_document_update(&self, document_id: &str) -> Vec<u8> {
let workspace_id = self.user_manager.workspace_id().unwrap();
let cloud_service = self.document_manager.get_cloud_service().clone();
let remote_updates = cloud_service
.get_document_updates(document_id, &workspace_id)
.await
.unwrap();
if remote_updates.is_empty() {
return vec![];
}
let updates = remote_updates
.iter()
.map(|update| update.as_ref())
.collect::<Vec<&[u8]>>();
merge_updates_v1(&updates).unwrap()
}
}
pub fn assert_document_data_equal(collab_update: &[u8], doc_id: &str, expected: DocumentData) {
let collab = MutexCollab::new(CollabOrigin::Server, doc_id, vec![]);
collab.lock().with_origin_transact_mut(|txn| {
let update = Update::decode_v1(collab_update).unwrap();
txn.apply_update(update);
});
let document = Document::open(Arc::new(collab)).unwrap();
let actual = document.get_document_data().unwrap();
assert_eq!(actual, expected);
}

View File

@ -0,0 +1,173 @@
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::errors::FlowyError;
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
// Must sign up/ sign in first
pub async fn get_current_workspace(&self) -> WorkspaceSettingPB {
EventBuilder::new(self.clone())
.event(FolderEvent::GetCurrentWorkspace)
.async_send()
.await
.parse::<WorkspaceSettingPB>()
}
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadWorkspaceViews)
.async_send()
.await
.parse::<RepeatedViewPB>()
.items
}
pub async fn get_views(&self, parent_view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: parent_view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn delete_view(&self, view_id: &str) {
let payload = RepeatedViewIdPB {
items: vec![view_id.to_string()],
};
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::DeleteView)
.payload(payload)
.async_send()
.await;
}
pub async fn update_view(&self, changeset: UpdateViewPayloadPB) -> Option<FlowyError> {
// delete the view. the view will be moved to trash
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateView)
.payload(changeset)
.async_send()
.await
.error()
}
pub async fn update_view_icon(&self, payload: UpdateViewIconPayloadPB) -> Option<FlowyError> {
EventBuilder::new(self.clone())
.event(FolderEvent::UpdateViewIcon)
.payload(payload)
.async_send()
.await
.error()
}
pub async fn create_view(&self, parent_id: &str, name: String) -> ViewPB {
let payload = CreateViewPayloadPB {
parent_view_id: parent_id.to_string(),
name,
desc: "".to_string(),
thumbnail: None,
layout: Default::default(),
initial_data: vec![],
meta: Default::default(),
set_as_current: false,
index: None,
};
EventBuilder::new(self.clone())
.event(FolderEvent::CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn get_view(&self, view_id: &str) -> ViewPB {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadView)
.payload(ViewIdPB {
value: view_id.to_string(),
})
.async_send()
.await
.parse::<ViewPB>()
}
}
pub struct ViewTest {
pub sdk: EventIntegrationTest,
pub workspace: WorkspacePB,
pub child_view: ViewPB,
}
impl ViewTest {
#[allow(dead_code)]
pub async fn new(sdk: &EventIntegrationTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
let workspace = create_workspace(sdk, "Workspace", "").await;
let payload = WorkspaceIdPB {
value: Some(workspace.id.clone()),
};
let _ = EventBuilder::new(sdk.clone())
.event(OpenWorkspace)
.payload(payload)
.async_send()
.await;
let payload = CreateViewPayloadPB {
parent_view_id: workspace.id.clone(),
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
layout,
initial_data: data,
meta: Default::default(),
set_as_current: true,
index: None,
};
let view = EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(payload)
.async_send()
.await
.parse::<ViewPB>();
Self {
sdk: sdk.clone(),
workspace,
child_view: view,
}
}
pub async fn new_grid_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Grid, data).await
}
pub async fn new_board_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Board, data).await
}
pub async fn new_calendar_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Calendar, data).await
}
}
async fn create_workspace(sdk: &EventIntegrationTest, name: &str, desc: &str) -> WorkspacePB {
let request = CreateWorkspacePayloadPB {
name: name.to_owned(),
desc: desc.to_owned(),
};
EventBuilder::new(sdk.clone())
.event(CreateWorkspace)
.payload(request)
.async_send()
.await
.parse::<WorkspacePB>()
}

View File

@ -0,0 +1,296 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::Arc;
use bytes::Bytes;
use nanoid::nanoid;
use protobuf::ProtobufError;
use tokio::sync::broadcast::{channel, Sender};
use uuid::Uuid;
use flowy_notification::entities::SubscribeObject;
use flowy_notification::NotificationSender;
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_SIGN_IN_URL, USER_UUID};
use flowy_user::entities::{
AuthTypePB, OauthSignInPB, SignInUrlPB, SignInUrlPayloadPB, SignUpPayloadPB, UpdateCloudConfigPB,
UpdateUserProfilePayloadPB, UserCloudConfigPB, UserProfilePB,
};
use flowy_user::errors::{FlowyError, FlowyResult};
use flowy_user::event_map::UserEvent::*;
use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
impl EventIntegrationTest {
pub async fn enable_encryption(&self) -> String {
let config = EventBuilder::new(self.clone())
.event(GetCloudConfig)
.async_send()
.await
.parse::<UserCloudConfigPB>();
let update = UpdateCloudConfigPB {
enable_sync: None,
enable_encrypt: Some(true),
};
let error = EventBuilder::new(self.clone())
.event(SetCloudConfig)
.payload(update)
.async_send()
.await
.error();
assert!(error.is_none());
config.encrypt_secret
}
pub async fn new_with_guest_user() -> Self {
let test = Self::default();
test.sign_up_as_guest().await;
test
}
pub async fn sign_up_as_guest(&self) -> SignUpContext {
let password = login_password();
let email = unique_email();
let payload = SignUpPayloadPB {
email,
name: "appflowy".to_string(),
password: password.clone(),
auth_type: AuthTypePB::Local,
device_id: uuid::Uuid::new_v4().to_string(),
}
.into_bytes()
.unwrap();
let request = AFPluginRequest::new(SignUp).payload(payload);
let user_profile = AFPluginDispatcher::async_send(self.inner.dispatcher(), request)
.await
.parse::<UserProfilePB, FlowyError>()
.unwrap()
.unwrap();
// let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
SignUpContext {
user_profile,
password,
}
}
pub async fn af_cloud_sign_up(&self) -> UserProfilePB {
let email = unique_email();
self.af_cloud_sign_in_with_email(&email).await.unwrap()
}
pub async fn supabase_party_sign_up(&self) -> UserProfilePB {
let map = third_party_sign_up_param(Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.parse::<UserProfilePB>()
}
pub async fn sign_out(&self) {
EventBuilder::new(self.clone())
.event(SignOut)
.async_send()
.await;
}
pub fn set_auth_type(&self, auth_type: AuthTypePB) {
*self.auth_type.write() = auth_type;
}
pub async fn init_user(&self) -> UserProfilePB {
self.sign_up_as_guest().await.user_profile
}
pub async fn get_user_profile(&self) -> Result<UserProfilePB, FlowyError> {
EventBuilder::new(self.clone())
.event(GetUserProfile)
.async_send()
.await
.try_parse::<UserProfilePB>()
}
pub async fn update_user_profile(&self, params: UpdateUserProfilePayloadPB) {
EventBuilder::new(self.clone())
.event(UpdateUserProfile)
.payload(params)
.async_send()
.await;
}
pub async fn af_cloud_sign_in_with_email(&self, email: &str) -> FlowyResult<UserProfilePB> {
let payload = SignInUrlPayloadPB {
email: email.to_string(),
auth_type: AuthTypePB::AFCloud,
};
let sign_in_url = EventBuilder::new(self.clone())
.event(GetSignInURL)
.payload(payload)
.async_send()
.await
.try_parse::<SignInUrlPB>()?
.sign_in_url;
let mut map = HashMap::new();
map.insert(USER_SIGN_IN_URL.to_string(), sign_in_url);
map.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::AFCloud,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
pub async fn supabase_sign_up_with_uuid(
&self,
uuid: &str,
email: Option<String>,
) -> FlowyResult<UserProfilePB> {
let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid.to_string());
map.insert(USER_DEVICE_ID.to_string(), uuid.to_string());
map.insert(
USER_EMAIL.to_string(),
email.unwrap_or_else(|| format!("{}@appflowy.io", nanoid!(10))),
);
let payload = OauthSignInPB {
map,
auth_type: AuthTypePB::Supabase,
};
let user_profile = EventBuilder::new(self.clone())
.event(OauthSignIn)
.payload(payload)
.async_send()
.await
.try_parse::<UserProfilePB>()?;
Ok(user_profile)
}
}
#[derive(Clone)]
pub struct TestNotificationSender {
sender: Arc<Sender<SubscribeObject>>,
}
impl Default for TestNotificationSender {
fn default() -> Self {
let (sender, _) = channel(1000);
Self {
sender: Arc::new(sender),
}
}
}
impl TestNotificationSender {
pub fn new() -> Self {
Self::default()
}
pub fn subscribe<T>(&self, id: &str, ty: impl Into<i32> + Send) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
let ty = ty.into();
tokio::spawn(async move {
// DatabaseNotification::DidUpdateDatabaseSnapshotState
while let Ok(value) = receiver.recv().await {
if value.id == id && value.ty == ty {
if let Some(payload) = value.payload {
match T::try_from(Bytes::from(payload)) {
Ok(object) => {
let _ = tx.send(object).await;
},
Err(e) => {
panic!(
"Failed to parse notification payload to type: {:?} with error: {}",
std::any::type_name::<T>(),
e
);
},
}
}
}
}
});
rx
}
pub fn subscribe_with_condition<T, F>(&self, id: &str, when: F) -> tokio::sync::mpsc::Receiver<T>
where
T: TryFrom<Bytes, Error = ProtobufError> + Send + 'static,
F: Fn(&T) -> bool + Send + 'static,
{
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
tokio::spawn(async move {
while let Ok(value) = receiver.recv().await {
if value.id == id {
if let Some(payload) = value.payload {
if let Ok(object) = T::try_from(Bytes::from(payload)) {
if when(&object) {
let _ = tx.send(object).await;
}
}
}
}
}
});
rx
}
}
impl NotificationSender for TestNotificationSender {
fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> {
let _ = self.sender.send(subject);
Ok(())
}
}
pub fn third_party_sign_up_param(uuid: String) -> HashMap<String, String> {
let mut params = HashMap::new();
params.insert(USER_UUID.to_string(), uuid);
params.insert(
USER_EMAIL.to_string(),
format!("{}@test.com", Uuid::new_v4()),
);
params.insert(USER_DEVICE_ID.to_string(), Uuid::new_v4().to_string());
params
}
pub fn unique_email() -> String {
format!("{}@appflowy.io", Uuid::new_v4())
}
pub fn login_email() -> String {
"annie2@appflowy.io".to_string()
}
pub fn login_password() -> String {
"HelloWorld!123".to_string()
}
pub struct SignUpContext {
pub user_profile: UserProfilePB,
pub password: String,
}

View File

@ -1,84 +0,0 @@
use std::sync::Arc;
use nanoid::nanoid;
use flowy_user::entities::{AuthTypePB, SignUpPayloadPB, UserProfilePB};
use flowy_user::errors::FlowyError;
use flowy_user::event_map::UserEvent::*;
use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
pub fn random_email() -> String {
format!("{}@appflowy.io", nanoid!(20))
}
pub fn login_email() -> String {
"annie2@appflowy.io".to_string()
}
pub fn login_password() -> String {
"HelloWorld!123".to_string()
}
pub struct SignUpContext {
pub user_profile: UserProfilePB,
pub password: String,
}
pub fn sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
let password = login_password();
let payload = SignUpPayloadPB {
email: random_email(),
name: "app flowy".to_string(),
password: password.clone(),
auth_type: AuthTypePB::Local,
device_id: uuid::Uuid::new_v4().to_string(),
}
.into_bytes()
.unwrap();
let request = AFPluginRequest::new(SignUp).payload(payload);
let user_profile = AFPluginDispatcher::sync_send(dispatch, request)
.parse::<UserProfilePB, FlowyError>()
.unwrap()
.unwrap();
SignUpContext {
user_profile,
password,
}
}
pub async fn async_sign_up(
dispatch: Arc<AFPluginDispatcher>,
auth_type: AuthTypePB,
) -> SignUpContext {
let password = login_password();
let email = random_email();
let payload = SignUpPayloadPB {
email,
name: "appflowy".to_string(),
password: password.clone(),
auth_type,
device_id: uuid::Uuid::new_v4().to_string(),
}
.into_bytes()
.unwrap();
let request = AFPluginRequest::new(SignUp).payload(payload);
let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request)
.await
.parse::<UserProfilePB, FlowyError>()
.unwrap()
.unwrap();
// let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
SignUpContext {
user_profile,
password,
}
}
pub async fn init_user_setting(dispatch: Arc<AFPluginDispatcher>) {
let request = AFPluginRequest::new(InitUser);
let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await;
}

View File

@ -3,7 +3,7 @@ use std::convert::TryFrom;
use bytes::Bytes;
use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_database2::entities::{
CellChangesetPB, CellIdPB, ChecklistCellDataChangesetPB, DatabaseLayoutPB,
DatabaseSettingChangesetPB, DatabaseViewIdPB, DateChangesetPB, FieldType, SelectOptionCellDataPB,
@ -13,7 +13,7 @@ use lib_infra::util::timestamp;
#[tokio::test]
async fn get_database_id_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -35,7 +35,7 @@ async fn get_database_id_event_test() {
#[tokio::test]
async fn get_database_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -49,7 +49,7 @@ async fn get_database_event_test() {
#[tokio::test]
async fn get_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -64,7 +64,7 @@ async fn get_field_event_test() {
#[tokio::test]
async fn create_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -78,7 +78,7 @@ async fn create_field_event_test() {
#[tokio::test]
async fn delete_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -99,7 +99,7 @@ async fn delete_field_event_test() {
// The primary field is not allowed to be deleted.
#[tokio::test]
async fn delete_primary_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -114,7 +114,7 @@ async fn delete_primary_field_event_test() {
#[tokio::test]
async fn update_field_type_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -132,7 +132,7 @@ async fn update_field_type_event_test() {
#[tokio::test]
async fn update_primary_field_type_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -151,7 +151,7 @@ async fn update_primary_field_type_event_test() {
#[tokio::test]
async fn duplicate_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -169,7 +169,7 @@ async fn duplicate_field_event_test() {
// The primary field is not allowed to be duplicated. So this test should return an error.
#[tokio::test]
async fn duplicate_primary_field_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -183,7 +183,7 @@ async fn duplicate_primary_field_test() {
#[tokio::test]
async fn get_primary_field_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -196,7 +196,7 @@ async fn get_primary_field_event_test() {
#[tokio::test]
async fn create_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -209,7 +209,7 @@ async fn create_row_event_test() {
#[tokio::test]
async fn delete_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -232,7 +232,7 @@ async fn delete_row_event_test() {
#[tokio::test]
async fn get_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -248,7 +248,7 @@ async fn get_row_event_test() {
#[tokio::test]
async fn update_row_meta_event_with_url_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -276,7 +276,7 @@ async fn update_row_meta_event_with_url_test() {
#[tokio::test]
async fn update_row_meta_event_with_cover_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -304,7 +304,7 @@ async fn update_row_meta_event_with_cover_test() {
#[tokio::test]
async fn delete_row_event_with_invalid_row_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -317,7 +317,7 @@ async fn delete_row_event_with_invalid_row_id_test() {
#[tokio::test]
async fn duplicate_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -334,7 +334,7 @@ async fn duplicate_row_event_test() {
#[tokio::test]
async fn duplicate_row_event_with_invalid_row_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -351,7 +351,7 @@ async fn duplicate_row_event_with_invalid_row_id_test() {
#[tokio::test]
async fn move_row_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -371,7 +371,7 @@ async fn move_row_event_test() {
#[tokio::test]
async fn move_row_event_test2() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -391,7 +391,7 @@ async fn move_row_event_test2() {
#[tokio::test]
async fn move_row_event_with_invalid_row_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -418,7 +418,7 @@ async fn move_row_event_with_invalid_row_id_test() {
#[tokio::test]
async fn update_text_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -448,7 +448,7 @@ async fn update_text_cell_event_test() {
#[tokio::test]
async fn update_checkbox_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -479,7 +479,7 @@ async fn update_checkbox_cell_event_test() {
#[tokio::test]
async fn update_single_select_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -506,7 +506,7 @@ async fn update_single_select_cell_event_test() {
#[tokio::test]
async fn update_date_cell_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -543,7 +543,7 @@ async fn update_date_cell_event_test() {
#[tokio::test]
async fn update_date_cell_event_with_empty_time_str_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -579,7 +579,7 @@ async fn update_date_cell_event_with_empty_time_str_test() {
#[tokio::test]
async fn create_checklist_field_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -600,7 +600,7 @@ async fn create_checklist_field_test() {
#[tokio::test]
async fn update_checklist_cell_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -657,7 +657,7 @@ async fn update_checklist_cell_test() {
// The number of groups should be 0 if there is no group by field in grid
#[tokio::test]
async fn get_groups_event_with_grid_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my board view".to_owned(), vec![])
@ -669,7 +669,7 @@ async fn get_groups_event_with_grid_test() {
#[tokio::test]
async fn get_groups_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -681,7 +681,7 @@ async fn get_groups_event_test() {
#[tokio::test]
async fn move_group_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -715,7 +715,7 @@ async fn move_group_event_test() {
#[tokio::test]
async fn move_group_event_with_invalid_id_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -737,7 +737,7 @@ async fn move_group_event_with_invalid_id_test() {
#[tokio::test]
async fn rename_group_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -761,7 +761,7 @@ async fn rename_group_event_test() {
#[tokio::test]
async fn hide_group_event_test2() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -783,7 +783,7 @@ async fn hide_group_event_test2() {
// Update the database layout type from grid to board
#[tokio::test]
async fn update_database_layout_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -805,7 +805,7 @@ async fn update_database_layout_event_test() {
// Update the database layout type from grid to board. Set the checkbox field as the grouping field
#[tokio::test]
async fn update_database_layout_event_test2() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -837,7 +837,7 @@ async fn update_database_layout_event_test2() {
// Create a checkbox field in the default board and then set it as the grouping field.
#[tokio::test]
async fn set_group_by_checkbox_field_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -854,7 +854,7 @@ async fn set_group_by_checkbox_field_test() {
#[tokio::test]
async fn get_all_calendar_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let calendar_view = test
.create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])
@ -867,7 +867,7 @@ async fn get_all_calendar_event_test() {
#[tokio::test]
async fn create_calendar_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let calendar_view = test
.create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])

View File

@ -1,6 +1,6 @@
use std::time::Duration;
use event_integration::assert_document_data_equal;
use event_integration::test_document::assert_document_data_equal;
use flowy_document2::entities::DocumentSyncStatePB;
use crate::document::af_cloud_test::util::AFCloudDocumentTest;

View File

@ -1,6 +1,6 @@
use std::time::Duration;
use event_integration::assert_document_data_equal;
use event_integration::test_document::assert_document_data_equal;
use flowy_document2::entities::DocumentSyncStatePB;
use crate::document::supabase_test::helper::FlowySupabaseDocumentTest;

View File

@ -1,7 +1,7 @@
use collab_folder::core::ViewLayout;
use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB};
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent::*;
@ -64,7 +64,7 @@ pub enum FolderScript {
}
pub struct FolderTest {
pub sdk: FlowyCoreTest,
pub sdk: EventIntegrationTest,
pub all_workspace: Vec<WorkspacePB>,
pub workspace: WorkspacePB,
pub parent_view: ViewPB,
@ -75,7 +75,7 @@ pub struct FolderTest {
impl FolderTest {
pub async fn new() -> Self {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await;
let workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await;
let parent_view = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await;
@ -201,7 +201,7 @@ impl FolderTest {
}
}
}
pub async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
pub async fn create_workspace(sdk: &EventIntegrationTest, name: &str, desc: &str) -> WorkspacePB {
let request = CreateWorkspacePayloadPB {
name: name.to_owned(),
desc: desc.to_owned(),
@ -215,7 +215,10 @@ pub async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> Wo
.parse::<WorkspacePB>()
}
pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -> Vec<WorkspacePB> {
pub async fn read_workspace(
sdk: &EventIntegrationTest,
workspace_id: Option<String>,
) -> Vec<WorkspacePB> {
let request = WorkspaceIdPB {
value: workspace_id,
};
@ -241,7 +244,12 @@ pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -
workspaces
}
pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
pub async fn create_app(
sdk: &EventIntegrationTest,
workspace_id: &str,
name: &str,
desc: &str,
) -> ViewPB {
let create_view_request = CreateViewPayloadPB {
parent_view_id: workspace_id.to_owned(),
name: name.to_string(),
@ -263,7 +271,7 @@ pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, des
}
pub async fn create_view(
sdk: &FlowyCoreTest,
sdk: &EventIntegrationTest,
app_id: &str,
name: &str,
desc: &str,
@ -288,7 +296,7 @@ pub async fn create_view(
.parse::<ViewPB>()
}
pub async fn read_view(sdk: &FlowyCoreTest, view_id: &str) -> ViewPB {
pub async fn read_view(sdk: &EventIntegrationTest, view_id: &str) -> ViewPB {
let view_id = ViewIdPB::from(view_id);
EventBuilder::new(sdk.clone())
.event(ReadView)
@ -299,7 +307,7 @@ pub async fn read_view(sdk: &FlowyCoreTest, view_id: &str) -> ViewPB {
}
pub async fn move_view(
sdk: &FlowyCoreTest,
sdk: &EventIntegrationTest,
view_id: String,
parent_id: String,
prev_view_id: Option<String>,
@ -319,7 +327,7 @@ pub async fn move_view(
assert!(error.is_none());
}
pub async fn update_view(
sdk: &FlowyCoreTest,
sdk: &EventIntegrationTest,
view_id: &str,
name: Option<String>,
desc: Option<String>,
@ -340,7 +348,7 @@ pub async fn update_view(
.await;
}
pub async fn update_view_icon(sdk: &FlowyCoreTest, view_id: &str, icon: Option<ViewIconPB>) {
pub async fn update_view_icon(sdk: &EventIntegrationTest, view_id: &str, icon: Option<ViewIconPB>) {
let request = UpdateViewIconPayloadPB {
view_id: view_id.to_string(),
icon,
@ -352,7 +360,7 @@ pub async fn update_view_icon(sdk: &FlowyCoreTest, view_id: &str, icon: Option<V
.await;
}
pub async fn delete_view(sdk: &FlowyCoreTest, view_ids: Vec<String>) {
pub async fn delete_view(sdk: &EventIntegrationTest, view_ids: Vec<String>) {
let request = RepeatedViewIdPB { items: view_ids };
EventBuilder::new(sdk.clone())
.event(DeleteView)
@ -361,7 +369,7 @@ pub async fn delete_view(sdk: &FlowyCoreTest, view_ids: Vec<String>) {
.await;
}
pub async fn read_trash(sdk: &FlowyCoreTest) -> RepeatedTrashPB {
pub async fn read_trash(sdk: &EventIntegrationTest) -> RepeatedTrashPB {
EventBuilder::new(sdk.clone())
.event(ReadTrash)
.async_send()
@ -369,7 +377,7 @@ pub async fn read_trash(sdk: &FlowyCoreTest) -> RepeatedTrashPB {
.parse::<RepeatedTrashPB>()
}
pub async fn restore_app_from_trash(sdk: &FlowyCoreTest, app_id: &str) {
pub async fn restore_app_from_trash(sdk: &EventIntegrationTest, app_id: &str) {
let id = TrashIdPB {
id: app_id.to_owned(),
};
@ -380,7 +388,7 @@ pub async fn restore_app_from_trash(sdk: &FlowyCoreTest, app_id: &str) {
.await;
}
pub async fn restore_view_from_trash(sdk: &FlowyCoreTest, view_id: &str) {
pub async fn restore_view_from_trash(sdk: &EventIntegrationTest, view_id: &str) {
let id = TrashIdPB {
id: view_id.to_owned(),
};
@ -391,14 +399,14 @@ pub async fn restore_view_from_trash(sdk: &FlowyCoreTest, view_id: &str) {
.await;
}
pub async fn delete_all_trash(sdk: &FlowyCoreTest) {
pub async fn delete_all_trash(sdk: &EventIntegrationTest) {
EventBuilder::new(sdk.clone())
.event(DeleteAllTrash)
.async_send()
.await;
}
pub async fn toggle_favorites(sdk: &FlowyCoreTest, view_id: Vec<String>) {
pub async fn toggle_favorites(sdk: &EventIntegrationTest, view_id: Vec<String>) {
let request = RepeatedViewIdPB { items: view_id };
EventBuilder::new(sdk.clone())
.event(ToggleFavorite)
@ -407,7 +415,7 @@ pub async fn toggle_favorites(sdk: &FlowyCoreTest, view_id: Vec<String>) {
.await;
}
pub async fn read_favorites(sdk: &FlowyCoreTest) -> RepeatedViewPB {
pub async fn read_favorites(sdk: &EventIntegrationTest) -> RepeatedViewPB {
EventBuilder::new(sdk.clone())
.event(ReadFavorites)
.async_send()

View File

@ -1,6 +1,6 @@
use std::time::Duration;
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_folder2::entities::{ChildViewUpdatePB, RepeatedViewPB, UpdateViewPayloadPB};
use flowy_folder2::notification::FolderNotification;
@ -16,7 +16,7 @@ use crate::util::receive_with_timeout;
/// 5. Await the notification for workspace view updates with a timeout of 30 seconds.
/// 6. Ensure that the received views contain the newly created "test_view".
async fn create_child_view_in_workspace_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let workspace = test.get_current_workspace().await.workspace;
let mut rx = test
.notification_sender
@ -40,7 +40,7 @@ async fn create_child_view_in_workspace_subscription_test() {
#[tokio::test]
async fn create_child_view_in_view_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let mut workspace = test.get_current_workspace().await.workspace;
let workspace_child_view = workspace.views.pop().unwrap();
let mut rx = test.notification_sender.subscribe::<ChildViewUpdatePB>(
@ -72,7 +72,7 @@ async fn create_child_view_in_view_subscription_test() {
#[tokio::test]
async fn delete_view_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let workspace = test.get_current_workspace().await.workspace;
let mut rx = test
.notification_sender
@ -94,7 +94,7 @@ async fn delete_view_subscription_test() {
#[tokio::test]
async fn update_view_subscription_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let mut workspace = test.get_current_workspace().await.workspace;
let mut rx = test
.notification_sender

View File

@ -1,12 +1,12 @@
use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB, ViewIconTypePB};
use flowy_folder2::entities::*;
use flowy_user::errors::ErrorCode;
#[tokio::test]
async fn create_workspace_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let request = CreateWorkspacePayloadPB {
name: "my second workspace".to_owned(),
desc: "".to_owned(),
@ -22,7 +22,7 @@ async fn create_workspace_event_test() {
#[tokio::test]
async fn open_workspace_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let payload = CreateWorkspacePayloadPB {
name: "my second workspace".to_owned(),
desc: "".to_owned(),
@ -52,7 +52,7 @@ async fn open_workspace_event_test() {
#[tokio::test]
async fn create_view_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -64,7 +64,7 @@ async fn create_view_event_test() {
#[tokio::test]
async fn update_view_event_with_name_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -85,7 +85,7 @@ async fn update_view_event_with_name_test() {
#[tokio::test]
async fn update_view_icon_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -109,7 +109,7 @@ async fn update_view_icon_event_test() {
#[tokio::test]
async fn delete_view_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -132,7 +132,7 @@ async fn delete_view_event_test() {
#[tokio::test]
async fn put_back_trash_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -175,7 +175,7 @@ async fn put_back_trash_event_test() {
#[tokio::test]
async fn delete_view_permanently_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -224,7 +224,7 @@ async fn delete_view_permanently_event_test() {
#[tokio::test]
async fn delete_all_trash_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
for i in 0..3 {
@ -268,7 +268,7 @@ async fn delete_all_trash_test() {
#[tokio::test]
async fn multiple_hierarchy_view_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..4 {
let parent = test
@ -344,7 +344,7 @@ async fn multiple_hierarchy_view_test() {
#[tokio::test]
async fn move_view_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..4 {
let parent = test
@ -382,7 +382,7 @@ async fn move_view_event_test() {
#[tokio::test]
async fn move_view_event_after_delete_view_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..6 {
let _ = test
@ -424,7 +424,7 @@ async fn move_view_event_after_delete_view_test() {
#[tokio::test]
async fn move_view_event_after_delete_view_test2() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let parent = test
.create_view(&current_workspace.id, "My view".to_string())
@ -466,7 +466,7 @@ async fn move_view_event_after_delete_view_test2() {
#[tokio::test]
async fn create_parent_view_with_invalid_name() {
for (name, code) in invalid_workspace_name_test_case() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let request = CreateWorkspacePayloadPB {
name,
desc: "".to_owned(),
@ -494,7 +494,7 @@ fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> {
#[tokio::test]
async fn move_view_across_parent_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let parent_1 = test
.create_view(&current_workspace.id, "My view 1".to_string())
@ -539,7 +539,7 @@ async fn move_view_across_parent_test() {
}
async fn move_folder_nested_view(
sdk: FlowyCoreTest,
sdk: EventIntegrationTest,
view_id: String,
new_parent_id: String,
prev_view_id: Option<String>,

View File

@ -1,13 +1,36 @@
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_user::entities::UpdateUserProfilePayloadPB;
use crate::util::{generate_test_email, get_af_cloud_config};
#[tokio::test]
async fn af_cloud_sign_up_test() {
if get_af_cloud_config().is_some() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let email = generate_test_email();
let user = test.af_cloud_sign_in_with_email(&email).await.unwrap();
assert_eq!(user.email, email);
}
}
#[tokio::test]
async fn af_cloud_update_user_metadata_of_open_ai_key() {
if get_af_cloud_config().is_some() {
let test = EventIntegrationTest::new();
let user = test.af_cloud_sign_up().await;
let old_profile = test.get_user_profile().await.unwrap();
assert_eq!(old_profile.openai_key, "".to_string());
test
.update_user_profile(UpdateUserProfilePayloadPB {
id: user.id,
openai_key: Some("new openai_key".to_string()),
..Default::default()
})
.await;
let new_profile = test.get_user_profile().await.unwrap();
assert_eq!(new_profile.openai_key, "new openai_key".to_string());
}
}

View File

@ -1,5 +1,5 @@
use event_integration::user_event::*;
use event_integration::{event_builder::EventBuilder, FlowyCoreTest};
use event_integration::test_user::{login_password, unique_email};
use event_integration::{event_builder::EventBuilder, EventIntegrationTest};
use flowy_user::entities::{AuthTypePB, SignInPayloadPB, SignUpPayloadPB};
use flowy_user::errors::ErrorCode;
use flowy_user::event_map::UserEvent::*;
@ -9,7 +9,7 @@ use crate::user::local_test::helper::*;
#[tokio::test]
async fn sign_up_with_invalid_email() {
for email in invalid_email_test_case() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let request = SignUpPayloadPB {
email: email.to_string(),
name: valid_name(),
@ -33,9 +33,9 @@ async fn sign_up_with_invalid_email() {
}
#[tokio::test]
async fn sign_up_with_long_password() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let request = SignUpPayloadPB {
email: random_email(),
email: unique_email(),
name: valid_name(),
password: "1234".repeat(100).as_str().to_string(),
auth_type: AuthTypePB::Local,
@ -58,7 +58,7 @@ async fn sign_up_with_long_password() {
#[tokio::test]
async fn sign_in_with_invalid_email() {
for email in invalid_email_test_case() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let request = SignInPayloadPB {
email: email.to_string(),
password: login_password(),
@ -84,10 +84,10 @@ async fn sign_in_with_invalid_email() {
#[tokio::test]
async fn sign_in_with_invalid_password() {
for password in invalid_password_test_case() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let request = SignInPayloadPB {
email: random_email(),
email: unique_email(),
password,
name: "".to_string(),
auth_type: AuthTypePB::Local,

View File

@ -1,13 +1,13 @@
use std::collections::HashMap;
use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_user::entities::{ReminderPB, RepeatedReminderPB};
use flowy_user::event_map::UserEvent::*;
#[tokio::test]
async fn user_update_with_name() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let _ = sdk.sign_up_as_guest().await;
let mut meta = HashMap::new();
meta.insert("object_id".to_string(), "".to_string());

View File

@ -1,14 +1,16 @@
use crate::user::local_test::helper::*;
use event_integration::{event_builder::EventBuilder, FlowyCoreTest};
use nanoid::nanoid;
use event_integration::{event_builder::EventBuilder, EventIntegrationTest};
use flowy_user::entities::{UpdateUserProfilePayloadPB, UserProfilePB};
use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
use nanoid::nanoid;
use crate::user::local_test::helper::*;
// use serial_test::*;
#[tokio::test]
async fn user_profile_get_failed() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let result = EventBuilder::new(sdk)
.event(GetUserProfile)
.async_send()
@ -19,7 +21,7 @@ async fn user_profile_get_failed() {
#[tokio::test]
async fn user_profile_get() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let user_profile = test.init_user().await;
let user = EventBuilder::new(test.clone())
.event(GetUserProfile)
@ -30,7 +32,7 @@ async fn user_profile_get() {
#[tokio::test]
async fn user_update_with_name() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let user = sdk.init_user().await;
let new_name = "hello_world".to_owned();
let request = UpdateUserProfilePayloadPB::new(user.id).name(&new_name);
@ -49,7 +51,7 @@ async fn user_update_with_name() {
#[tokio::test]
async fn user_update_with_ai_key() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let user = sdk.init_user().await;
let openai_key = "openai_key".to_owned();
let stability_ai_key = "stability_ai_key".to_owned();
@ -72,7 +74,7 @@ async fn user_update_with_ai_key() {
#[tokio::test]
async fn user_update_with_email() {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let user = sdk.init_user().await;
let new_email = format!("{}@gmail.com", nanoid!(6));
let request = UpdateUserProfilePayloadPB::new(user.id).email(&new_email);
@ -90,7 +92,7 @@ async fn user_update_with_email() {
#[tokio::test]
async fn user_update_with_invalid_email() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let user = test.init_user().await;
for email in invalid_email_test_case() {
let request = UpdateUserProfilePayloadPB::new(user.id).email(&email);
@ -109,7 +111,7 @@ async fn user_update_with_invalid_email() {
#[tokio::test]
async fn user_update_with_invalid_password() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let user = test.init_user().await;
for password in invalid_password_test_case() {
let request = UpdateUserProfilePayloadPB::new(user.id).password(&password);
@ -126,7 +128,7 @@ async fn user_update_with_invalid_password() {
#[tokio::test]
async fn user_update_with_invalid_name() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let user = test.init_user().await;
let request = UpdateUserProfilePayloadPB::new(user.id).name("");
assert!(EventBuilder::new(test.clone())

View File

@ -1,4 +1,4 @@
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_folder2::entities::ViewLayoutPB;
@ -11,7 +11,7 @@ async fn migrate_historical_empty_document_test() {
"historical_empty_document",
)
.unwrap();
let test = FlowyCoreTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let test = EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 3);

View File

@ -1,4 +1,4 @@
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_folder2::entities::ViewLayoutPB;
@ -11,7 +11,7 @@ async fn migrate_020_historical_empty_document_test() {
"020_historical_user_data",
)
.unwrap();
let test = FlowyCoreTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let test = EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let mut views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 1);

View File

@ -10,7 +10,7 @@ use serde_json::json;
use event_integration::document::document_event::DocumentEventTest;
use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_encrypt::decrypt_text;
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
@ -23,7 +23,7 @@ use crate::util::*;
#[tokio::test]
async fn third_party_sign_up_test() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
map.insert(
@ -48,7 +48,7 @@ async fn third_party_sign_up_test() {
#[tokio::test]
async fn third_party_sign_up_with_encrypt_test() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
test.supabase_party_sign_up().await;
let user_profile = test.get_user_profile().await.unwrap();
assert!(user_profile.encryption_sign.is_empty());
@ -65,7 +65,7 @@ async fn third_party_sign_up_with_encrypt_test() {
#[tokio::test]
async fn third_party_sign_up_with_duplicated_uuid() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let email = format!("{}@appflowy.io", nanoid!(6));
let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
@ -98,7 +98,7 @@ async fn third_party_sign_up_with_duplicated_uuid() {
#[tokio::test]
async fn third_party_sign_up_with_duplicated_email() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let email = format!("{}@appflowy.io", nanoid!(6));
test
.supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
@ -116,7 +116,7 @@ async fn third_party_sign_up_with_duplicated_email() {
#[tokio::test]
async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let old_views = test
.folder_manager
.get_current_workspace_views()
@ -148,7 +148,7 @@ async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
#[tokio::test]
async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_with_guest_user().await;
let uuid = uuid::Uuid::new_v4().to_string();
let email = format!("{}@appflowy.io", nanoid!(6));
@ -260,7 +260,7 @@ async fn update_user_profile_with_existing_email_test() {
#[tokio::test]
async fn migrate_anon_document_on_cloud_signup() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let user_profile = test.sign_up_as_guest().await.user_profile;
let view = test
@ -305,7 +305,8 @@ async fn migrate_anon_data_on_cloud_signup() {
"workspace_sync",
)
.unwrap();
let test = FlowyCoreTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string());
let user_profile = test.supabase_party_sign_up().await;
// Get the folder data from remote

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
use event_integration::{event_builder::EventBuilder, FlowyCoreTest};
use event_integration::{event_builder::EventBuilder, EventIntegrationTest};
use flowy_folder2::entities::WorkspaceSettingPB;
use flowy_folder2::event_map::FolderEvent::GetCurrentWorkspace;
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
@ -12,7 +12,7 @@ use crate::util::*;
#[tokio::test]
async fn initial_workspace_test() {
if get_supabase_config().is_some() {
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
let mut map = HashMap::new();
map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
map.insert(

View File

@ -16,7 +16,7 @@ use zip::ZipArchive;
use event_integration::event_builder::EventBuilder;
use event_integration::Cleaner;
use event_integration::FlowyCoreTest;
use event_integration::EventIntegrationTest;
use flowy_database_deps::cloud::DatabaseCloudService;
use flowy_folder_deps::cloud::{FolderCloudService, FolderSnapshot};
use flowy_server::supabase::api::*;
@ -36,13 +36,13 @@ pub fn get_supabase_config() -> Option<SupabaseConfiguration> {
}
pub struct FlowySupabaseTest {
inner: FlowyCoreTest,
inner: EventIntegrationTest,
}
impl FlowySupabaseTest {
pub fn new() -> Option<Self> {
let _ = get_supabase_config()?;
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
test.set_auth_type(AuthTypePB::Supabase);
test.server_provider.set_auth_type(AuthType::Supabase);
@ -76,7 +76,7 @@ impl FlowySupabaseTest {
}
impl Deref for FlowySupabaseTest {
type Target = FlowyCoreTest;
type Target = EventIntegrationTest;
fn deref(&self) -> &Self::Target {
&self.inner
@ -215,13 +215,13 @@ pub fn unzip_history_user_db(root: &str, folder_name: &str) -> std::io::Result<(
}
pub struct AFCloudTest {
inner: FlowyCoreTest,
inner: EventIntegrationTest,
}
impl AFCloudTest {
pub fn new() -> Option<Self> {
let _ = get_af_cloud_config()?;
let test = FlowyCoreTest::new();
let test = EventIntegrationTest::new();
test.set_auth_type(AuthTypePB::AFCloud);
test.server_provider.set_auth_type(AuthType::AFCloud);
@ -230,7 +230,7 @@ impl AFCloudTest {
}
impl Deref for AFCloudTest {
type Target = FlowyCoreTest;
type Target = EventIntegrationTest;
fn deref(&self) -> &Self::Target {
&self.inner

View File

@ -132,7 +132,12 @@ impl DatabaseManager {
Ok(())
}
#[instrument(level = "debug", skip_all, err)]
#[instrument(
name = "database_initialize_with_new_user",
level = "debug",
skip_all,
err
)]
pub async fn initialize_with_new_user(
&self,
user_id: i64,

View File

@ -6,8 +6,8 @@ use collab_database::fields::Field;
use collab_database::rows::{CreateRowParams, RowDetail, RowId};
use strum::EnumCount;
use event_integration::folder_event::ViewTest;
use event_integration::FlowyCoreTest;
use event_integration::test_folder::ViewTest;
use event_integration::EventIntegrationTest;
use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB};
use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
use flowy_database2::services::database::DatabaseEditor;
@ -26,8 +26,7 @@ use crate::database::mock_data::{
};
pub struct DatabaseEditorTest {
pub sdk: FlowyCoreTest,
pub app_id: String,
pub sdk: EventIntegrationTest,
pub view_id: String,
pub editor: Arc<DatabaseEditor>,
pub fields: Vec<Arc<Field>>,
@ -38,7 +37,7 @@ pub struct DatabaseEditorTest {
impl DatabaseEditorTest {
pub async fn new_grid() -> Self {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await;
let params = make_test_grid();
@ -47,7 +46,7 @@ impl DatabaseEditorTest {
}
pub async fn new_no_date_grid() -> Self {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await;
let params = make_no_date_test_grid();
@ -56,7 +55,7 @@ impl DatabaseEditorTest {
}
pub async fn new_board() -> Self {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await;
let params = make_test_board();
@ -65,7 +64,7 @@ impl DatabaseEditorTest {
}
pub async fn new_calendar() -> Self {
let sdk = FlowyCoreTest::new();
let sdk = EventIntegrationTest::new();
let _ = sdk.init_user().await;
let params = make_test_calendar();
@ -73,7 +72,7 @@ impl DatabaseEditorTest {
Self::new(sdk, view_test).await
}
pub async fn new(sdk: FlowyCoreTest, test: ViewTest) -> Self {
pub async fn new(sdk: EventIntegrationTest, test: ViewTest) -> Self {
let editor = sdk
.database_manager
.get_database_with_view_id(&test.child_view.id)
@ -92,10 +91,8 @@ impl DatabaseEditorTest {
.collect();
let view_id = test.child_view.id;
let app_id = test.parent_view.id;
Self {
sdk,
app_id,
view_id,
editor,
fields,

View File

@ -59,7 +59,12 @@ impl DocumentManager {
Ok(())
}
#[instrument(level = "debug", skip_all, err)]
#[instrument(
name = "document_initialize_with_new_user",
level = "debug",
skip_all,
err
)]
pub async fn initialize_with_new_user(&self, uid: i64, workspace_id: String) -> FlowyResult<()> {
self.initialize(uid, workspace_id).await?;
Ok(())

View File

@ -29,6 +29,7 @@ impl std::convert::From<i32> for DocumentNotification {
}
}
#[tracing::instrument(level = "trace")]
pub(crate) fn send_notification(id: &str, ty: DocumentNotification) -> NotificationBuilder {
NotificationBuilder::new(id, ty, DOCUMENT_OBSERVABLE_SOURCE)
}

View File

@ -265,7 +265,12 @@ impl FolderManager {
/// Initialize the folder for the new user.
/// Using the [DefaultFolderBuilder] to create the default workspace for the new user.
#[instrument(level = "debug", skip_all, err)]
#[instrument(
name = "folder_initialize_with_new_user",
level = "debug",
skip_all,
err
)]
pub async fn initialize_with_new_user(
&self,
user_id: i64,

View File

@ -23,7 +23,7 @@ bytes = { version = "1.5", features = ["serde"] }
tokio-retry = "0.3"
anyhow = "1.0"
uuid = { version = "1.3.3", features = ["v4"] }
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
chrono = { version = "0.4.31", default-features = false, features = ["clock", "serde"] }
collab = { version = "0.1.0" }
collab-plugins = { version = "0.1.0"}
collab-document = { version = "0.1.0" }

View File

@ -2,11 +2,8 @@ use std::collections::HashMap;
use std::sync::Arc;
use anyhow::{anyhow, Error};
use client_api::entity::dto::auth_dto::UpdateUsernameParams;
use client_api::entity::dto::workspace_dto::CreateWorkspaceMember;
use client_api::entity::{
AFRole, AFUserProfileView, AFWorkspace, AFWorkspaces, InsertCollabParams, OAuthProvider,
};
use client_api::entity::workspace_dto::CreateWorkspaceMember;
use client_api::entity::{AFRole, AFWorkspace, InsertCollabParams, OAuthProvider};
use collab_entity::CollabObject;
use flowy_error::{ErrorCode, FlowyError};
@ -15,6 +12,10 @@ use flowy_user_deps::entities::*;
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,
};
use crate::af_cloud::impls::user::util::encryption_type_from_profile;
use crate::af_cloud::{AFCloudClient, AFServer};
use crate::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL};
@ -92,12 +93,7 @@ where
FutureResult::new(async move {
let client = try_get_client?;
client
.update_user(UpdateUsernameParams {
name: params.name,
email: params.email,
password: params.password,
metadata: None,
})
.update_user(af_update_from_update_params(params))
.await?;
Ok(())
})
@ -111,30 +107,18 @@ where
FutureResult::new(async move {
let client = try_get_client?;
let profile = client.get_profile().await?;
let encryption_type = encryption_type_from_profile(&profile);
Ok(Some(UserProfile {
email: profile.email.unwrap_or("".to_string()),
name: profile.name.unwrap_or("".to_string()),
token: client.get_token()?,
icon_url: "".to_owned(),
openai_key: "".to_owned(),
stability_ai_key: "".to_owned(),
workspace_id: match profile.latest_workspace_id {
Some(w) => w.to_string(),
None => "".to_string(),
},
auth_type: AuthType::AFCloud,
encryption_type,
uid: profile.uid.ok_or(anyhow!("no uid found"))?,
}))
let token = client.get_token()?;
let profile = user_profile_from_af_profile(token, profile)?;
Ok(Some(profile))
})
}
fn get_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
fn get_all_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let workspaces = try_get_client?.get_workspaces().await?;
Ok(to_user_workspaces(workspaces)?)
Ok(to_user_workspaces(workspaces.0)?)
})
}
@ -151,7 +135,7 @@ where
let client_token = client.access_token()?;
// compare and check
if uid != profile.uid.ok_or(anyhow!("expecting uid"))? {
if uid != profile.uid {
return Err(anyhow!("uid mismatch"));
}
if token != client_token {
@ -238,59 +222,42 @@ pub async fn user_sign_in_with_url(
params: AFCloudOAuthParams,
) -> Result<AuthResponse, FlowyError> {
let is_new_user = client.sign_in_with_url(&params.sign_in_url).await?;
let (profile, af_workspaces) = tokio::try_join!(client.get_profile(), client.get_workspaces())?;
let latest_workspace = to_user_workspace(
af_workspaces
.get_latest(&profile)
.or(af_workspaces.first().cloned())
.ok_or(anyhow!("no workspace found"))?,
)?;
let workspace_profile = client.get_user_workspace_info().await?;
let user_profile = workspace_profile.user_profile;
let user_workspaces = to_user_workspaces(af_workspaces)?;
let encryption_type = encryption_type_from_profile(&profile);
let latest_workspace = to_user_workspace(workspace_profile.visiting_workspace);
let user_workspaces = to_user_workspaces(workspace_profile.workspaces)?;
let encryption_type = encryption_type_from_profile(&user_profile);
Ok(AuthResponse {
user_id: profile.uid.ok_or(anyhow!("no uid found"))?,
name: profile.name.ok_or(anyhow!("no name found"))?,
user_id: user_profile.uid,
name: user_profile.name.unwrap_or_default(),
latest_workspace,
user_workspaces,
email: profile.email,
email: user_profile.email,
token: Some(client.get_token()?),
device_id: params.device_id,
encryption_type,
is_new_user,
updated_at: user_profile.updated_at,
metadata: user_profile.metadata,
})
}
fn encryption_type_from_profile(profile: &AFUserProfileView) -> EncryptionType {
match &profile.encryption_sign {
Some(e) => EncryptionType::SelfEncryption(e.to_string()),
None => EncryptionType::NoEncryption,
fn to_user_workspace(af_workspace: AFWorkspace) -> UserWorkspace {
UserWorkspace {
id: af_workspace.workspace_id.to_string(),
name: af_workspace.workspace_name,
created_at: af_workspace.created_at,
database_views_aggregate_id: af_workspace.database_storage_id.to_string(),
}
}
fn to_user_workspace(af_workspace: AFWorkspace) -> Result<UserWorkspace, FlowyError> {
Ok(UserWorkspace {
id: af_workspace.workspace_id.to_string(),
name: af_workspace
.workspace_name
.ok_or(anyhow!("no workspace_name found"))?,
created_at: af_workspace
.created_at
.ok_or(anyhow!("no created_at found"))?,
database_views_aggregate_id: af_workspace
.database_storage_id
.ok_or(anyhow!("no database_views_aggregate_id found"))?
.to_string(),
})
}
fn to_user_workspaces(af_workspaces: AFWorkspaces) -> Result<Vec<UserWorkspace>, FlowyError> {
let mut result = Vec::with_capacity(af_workspaces.len());
for item in af_workspaces.0.into_iter() {
let user_workspace = to_user_workspace(item)?;
result.push(user_workspace);
fn to_user_workspaces(workspaces: Vec<AFWorkspace>) -> Result<Vec<UserWorkspace>, FlowyError> {
let mut result = Vec::with_capacity(workspaces.len());
for item in workspaces.into_iter() {
result.push(to_user_workspace(item));
}
Ok(result)
}

View File

@ -0,0 +1,65 @@
use anyhow::Error;
use client_api::entity::auth_dto::{UpdateUserParams, UserMetaData};
use client_api::entity::AFUserProfile;
use flowy_user_deps::entities::{
AuthType, UpdateUserProfileParams, UserProfile, 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;
pub fn af_update_from_update_params(update: UpdateUserProfileParams) -> UpdateUserParams {
let mut user_metadata = UserMetaData::new();
if let Some(openai_key) = update.openai_key {
user_metadata.insert(USER_METADATA_OPEN_AI_KEY, openai_key);
}
if let Some(stability_ai_key) = update.stability_ai_key {
user_metadata.insert(USER_METADATA_STABILITY_AI_KEY, stability_ai_key);
}
if let Some(icon_url) = update.icon_url {
user_metadata.insert(USER_METADATA_ICON_URL, icon_url);
}
UpdateUserParams {
name: update.name,
email: update.email,
password: update.password,
metadata: Some(user_metadata),
}
}
pub fn user_profile_from_af_profile(
token: String,
profile: AFUserProfile,
) -> Result<UserProfile, Error> {
let encryption_type = encryption_type_from_profile(&profile);
let (icon_url, openai_key, stability_ai_key) = {
profile
.metadata
.map(|m| {
(
m.get(USER_METADATA_ICON_URL).map(|v| v.to_string()),
m.get(USER_METADATA_OPEN_AI_KEY).map(|v| v.to_string()),
m.get(USER_METADATA_STABILITY_AI_KEY).map(|v| v.to_string()),
)
})
.unwrap_or_default()
};
Ok(UserProfile {
email: profile.email.unwrap_or("".to_string()),
name: profile.name.unwrap_or("".to_string()),
token,
icon_url: icon_url.unwrap_or_default(),
openai_key: openai_key.unwrap_or_default(),
stability_ai_key: stability_ai_key.unwrap_or_default(),
workspace_id: profile.latest_workspace_id.to_string(),
auth_type: AuthType::AFCloud,
encryption_type,
uid: profile.uid,
updated_at: profile.updated_at,
})
}

View File

@ -0,0 +1,5 @@
pub use cloud_service_impl::*;
mod cloud_service_impl;
mod dto;
mod util;

View File

@ -0,0 +1,10 @@
use client_api::entity::AFUserProfile;
use flowy_user_deps::entities::EncryptionType;
pub fn encryption_type_from_profile(profile: &AFUserProfile) -> EncryptionType {
match &profile.encryption_sign {
Some(e) => EncryptionType::SelfEncryption(e.to_string()),
None => EncryptionType::NoEncryption,
}
}

View File

@ -11,6 +11,7 @@ use flowy_user_deps::entities::*;
use flowy_user_deps::DEFAULT_USER_NAME;
use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
use lib_infra::util::timestamp;
use crate::local_server::uid::UserIDGenerator;
use crate::local_server::LocalServerDB;
@ -46,6 +47,8 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
token: None,
device_id: params.device_id,
encryption_type: EncryptionType::NoEncryption,
updated_at: timestamp(),
metadata: None,
})
})
}
@ -69,6 +72,8 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
token: None,
device_id: params.device_id,
encryption_type: EncryptionType::NoEncryption,
updated_at: timestamp(),
metadata: None,
})
})
}
@ -104,7 +109,7 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
FutureResult::new(async { Ok(None) })
}
fn get_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
fn get_all_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
FutureResult::new(async { Ok(vec![]) })
}

View File

@ -129,6 +129,8 @@ where
token: None,
device_id: params.device_id,
encryption_type: EncryptionType::from_sign(&user_profile.encryption_sign),
updated_at: user_profile.updated_at.timestamp(),
metadata: None,
})
})
}
@ -158,6 +160,8 @@ where
token: None,
device_id: params.device_id,
encryption_type: EncryptionType::from_sign(&response.encryption_sign),
updated_at: response.updated_at.timestamp(),
metadata: None,
})
})
}
@ -220,12 +224,13 @@ where
workspace_id: response.latest_workspace_id,
auth_type: AuthType::Supabase,
encryption_type: EncryptionType::from_sign(&response.encryption_sign),
updated_at: response.updated_at.timestamp(),
})),
}
})
}
fn get_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
fn get_all_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move {
let postgrest = try_get_postgrest?;
@ -419,7 +424,7 @@ async fn get_user_profile(
) -> Result<Option<UserProfileResponse>, Error> {
let mut builder = postgrest
.from(USER_PROFILE_VIEW)
.select("uid, email, name, encryption_sign, latest_workspace_id");
.select("uid, email, name, encryption_sign, latest_workspace_id, updated_at");
match params {
GetUserProfileParams::Uid(uid) => builder = builder.eq("uid", uid.to_string()),

View File

@ -1,6 +1,7 @@
use std::fmt;
use std::fmt::Display;
use chrono::{DateTime, Utc};
use serde::Deserialize;
use serde_json::Value;
use uuid::Uuid;
@ -27,6 +28,8 @@ pub(crate) struct UserProfileResponse {
#[serde(deserialize_with = "deserialize_null_or_default")]
pub encryption_sign: String,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE user_table
DROP COLUMN updated_at;

View File

@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE user_table
ADD COLUMN updated_at BIGINT NOT NULL DEFAULT 0;

View File

@ -32,6 +32,7 @@ diesel::table! {
auth_type -> Integer,
encryption_type -> Text,
stability_ai_key -> Text,
updated_at -> BigInt,
}
}

View File

@ -95,7 +95,7 @@ pub trait UserCloudService: Send + Sync + 'static {
) -> FutureResult<Option<UserProfile>, FlowyError>;
/// Return the all the workspaces of the user
fn get_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error>;
fn get_all_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error>;
fn check_user(&self, credential: UserCredentials) -> FutureResult<(), Error>;

View File

@ -2,9 +2,15 @@ use std::str::FromStr;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_repr::*;
use uuid::Uuid;
pub const USER_METADATA_OPEN_AI_KEY: &str = "openai_key";
pub const USER_METADATA_STABILITY_AI_KEY: &str = "stability_ai_key";
pub const USER_METADATA_ICON_URL: &str = "icon_url";
pub const USER_METADATA_UPDATE_AT: &str = "updated_at";
pub trait UserAuthResponse {
fn user_id(&self) -> i64;
fn user_name(&self) -> &str;
@ -14,52 +20,8 @@ pub trait UserAuthResponse {
fn user_token(&self) -> Option<String>;
fn user_email(&self) -> Option<String>;
fn encryption_type(&self) -> EncryptionType;
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SignInResponse {
pub user_id: i64,
pub name: String,
pub latest_workspace: UserWorkspace,
pub user_workspaces: Vec<UserWorkspace>,
pub email: Option<String>,
pub token: Option<String>,
pub device_id: String,
pub encryption_type: EncryptionType,
}
impl UserAuthResponse for SignInResponse {
fn user_id(&self) -> i64 {
self.user_id
}
fn user_name(&self) -> &str {
&self.name
}
fn latest_workspace(&self) -> &UserWorkspace {
&self.latest_workspace
}
fn user_workspaces(&self) -> &[UserWorkspace] {
&self.user_workspaces
}
fn device_id(&self) -> &str {
&self.device_id
}
fn user_token(&self) -> Option<String> {
self.token.clone()
}
fn user_email(&self) -> Option<String> {
self.email.clone()
}
fn encryption_type(&self) -> EncryptionType {
self.encryption_type.clone()
}
fn metadata(&self) -> &Option<serde_json::Value>;
fn updated_at(&self) -> i64;
}
#[derive(Default, Serialize, Deserialize, Debug)]
@ -91,6 +53,8 @@ pub struct AuthResponse {
pub token: Option<String>,
pub device_id: String,
pub encryption_type: EncryptionType,
pub updated_at: i64,
pub metadata: Option<serde_json::Value>,
}
impl UserAuthResponse for AuthResponse {
@ -125,6 +89,14 @@ impl UserAuthResponse for AuthResponse {
fn encryption_type(&self) -> EncryptionType {
self.encryption_type.clone()
}
fn metadata(&self) -> &Option<Value> {
&self.metadata
}
fn updated_at(&self) -> i64 {
self.updated_at
}
}
#[derive(Clone, Debug)]
@ -196,6 +168,7 @@ pub struct UserProfile {
pub auth_type: AuthType,
// If the encryption_sign is not empty, which means the user has enabled the encryption.
pub encryption_type: EncryptionType,
pub updated_at: i64,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
@ -243,17 +216,37 @@ where
{
fn from(params: (&T, &AuthType)) -> Self {
let (value, auth_type) = params;
let (icon_url, openai_key, stability_ai_key) = {
value
.metadata()
.as_ref()
.map(|m| {
(
m.get(USER_METADATA_ICON_URL)
.map(|v| v.to_string())
.unwrap_or_default(),
m.get(USER_METADATA_OPEN_AI_KEY)
.map(|v| v.to_string())
.unwrap_or_default(),
m.get(USER_METADATA_STABILITY_AI_KEY)
.map(|v| v.to_string())
.unwrap_or_default(),
)
})
.unwrap_or_default()
};
Self {
uid: value.user_id(),
email: value.user_email().unwrap_or_default(),
name: value.user_name().to_owned(),
token: value.user_token().unwrap_or_default(),
icon_url: "".to_owned(),
openai_key: "".to_owned(),
icon_url,
openai_key,
workspace_id: value.latest_workspace().id.to_owned(),
auth_type: auth_type.clone(),
encryption_type: value.encryption_type(),
stability_ai_key: "".to_owned(),
stability_ai_key,
updated_at: value.updated_at(),
}
}
}

View File

@ -240,6 +240,9 @@ pub struct UserStatePB {
pub struct AuthStateChangedPB {
#[pb(index = 1)]
pub state: AuthStatePB,
#[pb(index = 2)]
pub message: String,
}
#[derive(ProtoBuf_Enum, Debug, Clone)]

View File

@ -173,9 +173,9 @@ pub struct UserSecretPB {
}
#[derive(Default, ProtoBuf)]
pub struct UserEncryptionSecretCheckPB {
pub struct UserEncryptionConfigurationPB {
#[pb(index = 1)]
pub is_need_secret: bool,
pub require_secret: bool,
}
impl From<UserCloudConfig> for UserCloudConfigPB {

View File

@ -2,6 +2,7 @@ use std::sync::Weak;
use std::{convert::TryInto, sync::Arc};
use serde_json::Value;
use tracing::event;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_sqlite::kv::StorePreferences;
@ -89,7 +90,7 @@ pub async fn check_user_handler(
Ok(())
}
#[tracing::instrument(level = "debug", skip(manager))]
#[tracing::instrument(level = "debug", skip(manager), err)]
pub async fn get_user_profile_handler(
manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<UserProfilePB, FlowyError> {
@ -105,6 +106,12 @@ pub async fn get_user_profile_handler(
}
});
event!(
tracing::Level::DEBUG,
"Get user profile: {:?}",
user_profile
);
data_result_ok(user_profile.into())
}
@ -330,7 +337,7 @@ pub async fn set_encrypt_secret_handler(
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn check_encrypt_secret_handler(
manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<UserEncryptionSecretCheckPB, FlowyError> {
) -> DataResult<UserEncryptionConfigurationPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let uid = manager.get_session()?.user_id;
let profile = manager.get_user_profile(uid).await?;
@ -346,7 +353,9 @@ pub async fn check_encrypt_secret_handler(
},
};
data_result_ok(UserEncryptionSecretCheckPB { is_need_secret })
data_result_ok(UserEncryptionConfigurationPB {
require_secret: is_need_secret,
})
}
#[tracing::instrument(level = "debug", skip_all, err)]

View File

@ -272,7 +272,7 @@ pub enum UserEvent {
#[event(input = "UserSecretPB")]
SetEncryptionSecret = 15,
#[event(output = "UserEncryptionSecretCheckPB")]
#[event(output = "UserEncryptionConfigurationPB")]
CheckEncryptionSign = 16,
/// Return the all the workspaces of the user

View File

@ -149,6 +149,7 @@ impl UserManager {
UserTokenState::Invalid => {
send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::InvalidAuth,
message: "Token is invalid".to_string(),
})
.send();
},
@ -256,6 +257,7 @@ impl UserManager {
}
send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::AuthStateSignIn,
message: "Sign in success".to_string(),
})
.send();
Ok(user_profile)
@ -387,6 +389,7 @@ impl UserManager {
send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::AuthStateSignIn,
message: "Sign up success".to_string(),
})
.send();
Ok(())
@ -402,7 +405,7 @@ impl UserManager {
tokio::spawn(async move {
match server.sign_out(None).await {
Ok(_) => {},
Err(e) => tracing::error!("Sign out failed: {:?}", e),
Err(e) => event!(tracing::Level::ERROR, "{:?}", e),
}
});
Ok(())
@ -421,8 +424,12 @@ impl UserManager {
) -> Result<(), FlowyError> {
let changeset = UserTableChangeset::new(params.clone());
let session = self.get_session()?;
save_user_profile_change(session.user_id, self.db_pool(session.user_id)?, changeset)?;
self.update_user(session.user_id, None, params).await?;
upsert_user_profile_change(session.user_id, self.db_pool(session.user_id)?, changeset)?;
let profile = self.get_user_profile(session.user_id).await?;
self
.update_user(session.user_id, profile.token, params)
.await?;
Ok(())
}
@ -462,15 +469,14 @@ impl UserManager {
.await?
.ok_or_else(|| FlowyError::new(ErrorCode::RecordNotFound, "User not found"))?;
if !is_user_encryption_sign_valid(old_user_profile, &new_user_profile.encryption_type.sign()) {
return Err(FlowyError::new(
ErrorCode::InvalidEncryptSecret,
"Invalid encryption sign",
));
if new_user_profile.updated_at > old_user_profile.updated_at {
check_encryption_sign(old_user_profile, &new_user_profile.encryption_type.sign());
// Save the new user profile
let changeset = UserTableChangeset::from_user_profile(new_user_profile.clone());
let _ = upsert_user_profile_change(uid, self.database.get_pool(uid)?, changeset);
}
let changeset = UserTableChangeset::from_user_profile(new_user_profile.clone());
let _ = save_user_profile_change(uid, self.database.get_pool(uid)?, changeset);
Ok(new_user_profile)
}
@ -501,13 +507,12 @@ impl UserManager {
async fn update_user(
&self,
uid: i64,
token: Option<String>,
token: String,
params: UpdateUserProfileParams,
) -> Result<(), FlowyError> {
let server = self.cloud_services.get_user_service()?;
let token = token.to_owned();
tokio::spawn(async move {
let credentials = UserCredentials::new(token, Some(uid), None);
let credentials = UserCredentials::new(Some(token), Some(uid), None);
server.update_user(credentials, params).await
})
.await
@ -613,6 +618,7 @@ impl UserManager {
) -> Result<(), FlowyError> {
let user_profile = UserProfile::from((response, auth_type));
let uid = user_profile.uid;
event!(tracing::Level::DEBUG, "Save new history user: {:?}", uid);
self.add_historical_user(
uid,
response.device_id(),
@ -620,7 +626,9 @@ impl UserManager {
auth_type,
self.user_dir(uid),
);
event!(tracing::Level::DEBUG, "Save new history user workspace");
save_user_workspaces(uid, self.db_pool(uid)?, response.user_workspaces())?;
event!(tracing::Level::INFO, "Save new user profile to disk");
self
.save_user(uid, (user_profile, auth_type.clone()).into())
.await?;
@ -640,12 +648,12 @@ impl UserManager {
if session.user_id == user_update.uid {
debug!("Receive user update: {:?}", user_update);
let user_profile = self.get_user_profile(user_update.uid).await?;
if !is_user_encryption_sign_valid(&user_profile, &user_update.encryption_sign) {
if !check_encryption_sign(&user_profile, &user_update.encryption_sign) {
return Ok(());
}
// Save the user profile change
save_user_profile_change(
upsert_user_profile_change(
user_update.uid,
self.db_pool(user_update.uid)?,
UserTableChangeset::from(user_update),
@ -685,24 +693,30 @@ impl UserManager {
}
}
fn is_user_encryption_sign_valid(user_profile: &UserProfile, encryption_sign: &str) -> bool {
fn check_encryption_sign(user_profile: &UserProfile, encryption_sign: &str) -> bool {
// If the local user profile's encryption sign is not equal to the user update's encryption sign,
// which means the user enable encryption in another device, we should logout the current user.
let is_valid = user_profile.encryption_type.sign() == encryption_sign;
if !is_valid {
send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::InvalidAuth,
message: "Encryption configuration was changed".to_string(),
})
.send();
}
is_valid
}
fn save_user_profile_change(
fn upsert_user_profile_change(
uid: i64,
pool: Arc<ConnectionPool>,
changeset: UserTableChangeset,
) -> FlowyResult<()> {
event!(
tracing::Level::DEBUG,
"Update user profile with changeset: {:?}",
changeset
);
let conn = pool.get()?;
diesel_update_table!(user_table, changeset, &*conn);
let user: UserProfile = user_table::dsl::user_table
@ -719,5 +733,5 @@ fn save_user_profile_change(
fn save_user_token(uid: i64, pool: Arc<ConnectionPool>, token: String) -> FlowyResult<()> {
let params = UpdateUserProfileParams::new(uid).with_token(token);
let changeset = UserTableChangeset::new(params);
save_user_profile_change(uid, pool, changeset)
upsert_user_profile_change(uid, pool, changeset)
}

View File

@ -21,6 +21,7 @@ impl std::convert::From<UserNotification> for i32 {
}
}
#[tracing::instrument(level = "trace")]
pub(crate) fn send_notification(id: &str, ty: UserNotification) -> NotificationBuilder {
NotificationBuilder::new(id, ty, USER_OBSERVABLE_SOURCE)
}

View File

@ -19,6 +19,7 @@ pub struct UserTable {
pub(crate) auth_type: i32,
pub(crate) encryption_type: String,
pub(crate) stability_ai_key: String,
pub(crate) updated_at: i64,
}
impl UserTable {
@ -43,6 +44,7 @@ impl From<(UserProfile, AuthType)> for UserTable {
auth_type: auth_type as i32,
encryption_type,
stability_ai_key: user_profile.stability_ai_key,
updated_at: user_profile.updated_at,
}
}
}
@ -60,6 +62,7 @@ impl From<UserTable> for UserProfile {
auth_type: AuthType::from(table.auth_type),
encryption_type: EncryptionType::from_str(&table.encryption_type).unwrap_or_default(),
stability_ai_key: table.stability_ai_key,
updated_at: table.updated_at,
}
}
}

View File

@ -74,7 +74,7 @@ impl UserManager {
if let Ok(service) = self.cloud_services.get_user_service() {
if let Ok(pool) = self.db_pool(uid) {
tokio::spawn(async move {
if let Ok(new_user_workspaces) = service.get_user_workspaces(uid).await {
if let Ok(new_user_workspaces) = service.get_all_user_workspaces(uid).await {
let _ = save_user_workspaces(uid, pool, &new_user_workspaces);
let repeated_workspace_pbs = RepeatedUserWorkspacePB::from(new_user_workspaces);
send_notification(&uid.to_string(), UserNotification::DidUpdateUserWorkspaces)

View File

@ -62,7 +62,7 @@ flutter test --dart-define=RUST_LOG=${RUST_LOG} -j, --concurrency=1 --coverage
script_runner = "@shell"
[tasks.rust_unit_test]
run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] }
run_task = { name = ["rust_lib_unit_test"] }
[tasks.supabase_unit_test]
env = { RUST_LOG = "info" }