diff --git a/frontend/rust-lib/flowy-core/Cargo.toml b/frontend/rust-lib/flowy-core/Cargo.toml index 3859b6f7b1..19fddb4443 100644 --- a/frontend/rust-lib/flowy-core/Cargo.toml +++ b/frontend/rust-lib/flowy-core/Cargo.toml @@ -44,8 +44,8 @@ crossbeam-utils = "0.8" chrono = "0.4" [dev-dependencies] -flowy-test = { path = "../flowy-test" } serial_test = "0.5.1" +flowy-test = { path = "../flowy-test" } [features] default = [] diff --git a/frontend/rust-lib/flowy-core/src/controller.rs b/frontend/rust-lib/flowy-core/src/controller.rs index e2b6b46436..e53a2ee2cf 100644 --- a/frontend/rust-lib/flowy-core/src/controller.rs +++ b/frontend/rust-lib/flowy-core/src/controller.rs @@ -1,13 +1,12 @@ use bytes::Bytes; use chrono::Utc; use flowy_collaboration::client_document::default::{initial_delta, initial_read_me}; -use flowy_core_data_model::{entities::view::CreateViewParams, user_default}; +use flowy_core_data_model::user_default; use flowy_document::context::DocumentContext; use flowy_sync::RevisionWebSocket; use lazy_static::lazy_static; -use futures_core::future::BoxFuture; - +use flowy_collaboration::folder::FolderPad; use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; @@ -16,11 +15,18 @@ use crate::{ entities::workspace::RepeatedWorkspace, errors::FlowyResult, module::{FolderCouldServiceV1, WorkspaceUser}, - services::{persistence::FolderPersistence, AppController, TrashController, ViewController, WorkspaceController}, + services::{ + persistence::FolderPersistence, + set_current_workspace, + AppController, + TrashController, + ViewController, + WorkspaceController, + }, }; lazy_static! { - static ref INIT_WORKSPACE: RwLock> = RwLock::new(HashMap::new()); + static ref INIT_FOLDER_FLAG: RwLock> = RwLock::new(HashMap::new()); } pub struct FolderManager { @@ -43,7 +49,7 @@ impl FolderManager { ws_sender: Arc, ) -> Self { if let Ok(token) = user.token() { - INIT_WORKSPACE.write().insert(token, false); + INIT_FOLDER_FLAG.write().insert(token, false); } let trash_controller = Arc::new(TrashController::new( @@ -97,74 +103,56 @@ impl FolderManager { pub async fn did_receive_ws_data(&self, _data: Bytes) {} - pub async fn initialize(&self, token: &str) -> FlowyResult<()> { - self.initialize_with_fn(token, || Box::pin(async { Ok(()) })).await?; - Ok(()) - } - - pub async fn clear(&self) { self.persistence.user_did_logout() } - - pub async fn initialize_with_new_user(&self, token: &str) -> FlowyResult<()> { - self.initialize_with_fn(token, || Box::pin(self.initial_default_workspace())) - .await - } - - async fn initialize_with_fn<'a, F>(&'a self, token: &str, f: F) -> FlowyResult<()> - where - F: FnOnce() -> BoxFuture<'a, FlowyResult<()>>, - { - if let Some(is_init) = INIT_WORKSPACE.read().get(token) { + pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> { + if let Some(is_init) = INIT_FOLDER_FLAG.read().get(user_id) { if *is_init { return Ok(()); } } - INIT_WORKSPACE.write().insert(token.to_owned(), true); - - self.persistence.initialize().await?; - f().await?; + let _ = self.persistence.initialize(user_id).await?; let _ = self.app_controller.initialize()?; let _ = self.view_controller.initialize()?; + INIT_FOLDER_FLAG.write().insert(user_id.to_owned(), true); Ok(()) } - async fn initial_default_workspace(&self) -> FlowyResult<()> { + pub async fn initialize_with_new_user(&self, user_id: &str, token: &str) -> FlowyResult<()> { + DefaultFolderBuilder::build(token, user_id, self.persistence.clone(), self.view_controller.clone()).await?; + self.initialize(user_id).await + } + + pub async fn clear(&self) { self.persistence.user_did_logout() } +} + +struct DefaultFolderBuilder(); +impl DefaultFolderBuilder { + async fn build( + token: &str, + user_id: &str, + persistence: Arc, + view_controller: Arc, + ) -> FlowyResult<()> { log::debug!("Create user default workspace"); let time = Utc::now(); let workspace = user_default::create_default_workspace(time); - let apps = workspace.apps.clone().into_inner(); - let cloned_workspace = workspace.clone(); - - let _ = self.workspace_controller.create_workspace_on_local(workspace).await?; - for app in apps { - let app_id = app.id.clone(); - let views = app.belongings.clone().into_inner(); - let _ = self.app_controller.create_app_on_local(app).await?; - for (index, view) in views.into_iter().enumerate() { + set_current_workspace(&workspace.id); + for app in workspace.apps.iter() { + for (index, view) in app.belongings.iter().enumerate() { let view_data = if index == 0 { initial_read_me().to_json() } else { initial_delta().to_json() }; - self.view_controller.set_latest_view(&view); - let params = CreateViewParams { - belong_to_id: app_id.clone(), - name: view.name, - desc: view.desc, - thumbnail: "".to_string(), - view_type: view.view_type, - view_data, - view_id: view.id.clone(), - }; - let _ = self.view_controller.create_view_from_params(params).await?; + view_controller.set_latest_view(&view); + let _ = view_controller + .create_view_document_content(&view.id, view_data) + .await?; } } - - let token = self.user.token()?; - let repeated_workspace = RepeatedWorkspace { - items: vec![cloned_workspace], - }; - - send_dart_notification(&token, WorkspaceNotification::UserCreateWorkspace) + let folder = FolderPad::new(vec![workspace.clone()], vec![])?; + let _ = persistence.save_folder(user_id, folder).await?; + let repeated_workspace = RepeatedWorkspace { items: vec![workspace] }; + send_dart_notification(token, WorkspaceNotification::UserCreateWorkspace) .payload(repeated_workspace) .send(); Ok(()) diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-core/src/services/persistence/migration.rs new file mode 100644 index 0000000000..f32021e565 --- /dev/null +++ b/frontend/rust-lib/flowy-core/src/services/persistence/migration.rs @@ -0,0 +1,81 @@ +use crate::{ + module::WorkspaceDatabase, + services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql, FOLDER_ID}, +}; +use flowy_collaboration::{ + entities::revision::{md5, Revision}, + folder::FolderPad, +}; +use flowy_core_data_model::entities::{ + app::{App, RepeatedApp}, + view::{RepeatedView, View}, + workspace::Workspace, +}; +use flowy_database::kv::KV; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_sync::{RevisionCache, RevisionManager}; +use std::sync::Arc; + +pub(crate) const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION"; + +pub(crate) struct FolderMigration { + user_id: String, + database: Arc, +} + +impl FolderMigration { + pub fn new(user_id: &str, database: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + database, + } + } + + pub fn run_v1_migration(&self) -> FlowyResult> { + let key = md5(format!("{}{}", self.user_id, V1_MIGRATION)); + if KV::get_bool(&key).unwrap_or(false) { + return Ok(None); + } + tracing::trace!("Run folder version 1 migrations"); + let pool = self.database.db_pool()?; + let conn = &*pool.get()?; + let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| { + let mut workspaces = WorkspaceTableSql::read_workspaces(&self.user_id, None, conn)? + .into_iter() + .map(Workspace::from) + .collect::>(); + + for workspace in workspaces.iter_mut() { + let mut apps = AppTableSql::read_workspace_apps(&workspace.id, conn)? + .into_iter() + .map(App::from) + .collect::>(); + + for app in apps.iter_mut() { + let views = ViewTableSql::read_views(&app.id, conn)? + .into_iter() + .map(View::from) + .collect::>(); + + app.belongings = RepeatedView { items: views }; + } + + workspace.apps = RepeatedApp { items: apps }; + } + Ok(workspaces) + })?; + + if workspaces.is_empty() { + return Ok(None); + } + + let trash = conn.immediate_transaction::<_, FlowyError, _>(|| { + let trash = TrashTableSql::read_all(conn)?.take_items(); + Ok(trash) + })?; + + let folder = FolderPad::new(workspaces, trash)?; + KV::set_bool(&key, true); + Ok(Some(folder)) + } +} diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs index dd25a63b26..e2011bdf7a 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs @@ -1,13 +1,18 @@ +mod migration; pub mod version_1; mod version_2; +use flowy_collaboration::{ + entities::revision::{Revision, RevisionState}, + folder::FolderPad, +}; use parking_lot::RwLock; use std::sync::Arc; pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*}; use crate::{ module::{WorkspaceDatabase, WorkspaceUser}, - services::persistence::version_2::v2_impl::FolderEditor, + services::persistence::{migration::FolderMigration, version_2::v2_impl::FolderEditor}, }; use flowy_core_data_model::entities::{ app::App, @@ -17,6 +22,9 @@ use flowy_core_data_model::entities::{ workspace::Workspace, }; use flowy_error::{FlowyError, FlowyResult}; +use flowy_sync::{mk_revision_disk_cache, RevisionCache, RevisionManager, RevisionRecord}; + +pub const FOLDER_ID: &str = "flowy_folder"; pub trait FolderPersistenceTransaction { fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()>; @@ -57,8 +65,12 @@ impl FolderPersistence { } } + #[deprecated( + since = "0.0.3", + note = "please use `begin_transaction` instead, this interface will be removed in the future" + )] #[allow(dead_code)] - pub fn begin_transaction2(&self, f: F) -> FlowyResult + pub fn begin_transaction_v_1(&self, f: F) -> FlowyResult where F: for<'a> FnOnce(Box) -> FlowyResult, { @@ -93,7 +105,13 @@ impl FolderPersistence { pub fn user_did_logout(&self) { *self.folder_editor.write() = None; } - pub async fn initialize(&self) -> FlowyResult<()> { + pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> { + let migrations = FolderMigration::new(user_id, self.database.clone()); + if let Some(migrated_folder) = migrations.run_v1_migration()? { + tracing::trace!("Save migration folder"); + self.save_folder(user_id, migrated_folder).await?; + } + let _ = self.init_folder_editor().await?; Ok(()) } @@ -107,4 +125,20 @@ impl FolderPersistence { *self.folder_editor.write() = Some(editor.clone()); Ok(editor) } + + pub async fn save_folder(&self, user_id: &str, folder: FolderPad) -> FlowyResult<()> { + let pool = self.database.db_pool()?; + let delta_data = folder.delta().to_bytes(); + let md5 = folder.md5(); + let revision = Revision::new(FOLDER_ID, 0, 0, delta_data, user_id, md5); + let record = RevisionRecord { + revision, + state: RevisionState::Sync, + write_to_disk: true, + }; + + let conn = pool.get()?; + let disk_cache = mk_revision_disk_cache(user_id, pool); + disk_cache.write_revision_records(vec![record], &conn) + } } diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/app_sql.rs b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/app_sql.rs index ad51dc2109..f808d0342c 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/app_sql.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/app_sql.rs @@ -41,12 +41,10 @@ impl AppTableSql { pub(crate) fn read_workspace_apps( workspace_id: &str, - is_trash: bool, conn: &SqliteConnection, ) -> Result, FlowyError> { let app_table = dsl::app_table .filter(app_table::workspace_id.eq(workspace_id)) - .filter(app_table::is_trash.eq(is_trash)) .order(app_table::create_time.asc()) .load::(conn)?; diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs index 4238aeb6ef..0633dc5686 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs @@ -23,7 +23,7 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> { } fn read_workspaces(&self, user_id: &str, workspace_id: Option) -> FlowyResult> { - let tables = WorkspaceTableSql::read_workspaces(workspace_id, user_id, &*self.0)?; + let tables = WorkspaceTableSql::read_workspaces(user_id, workspace_id, &*self.0)?; let workspaces = tables.into_iter().map(Workspace::from).collect::>(); Ok(workspaces) } @@ -52,7 +52,7 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> { } fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - let tables = AppTableSql::read_workspace_apps(workspace_id, false, &*self.0)?; + let tables = AppTableSql::read_workspace_apps(workspace_id, &*self.0)?; let apps = tables.into_iter().map(App::from).collect::>(); Ok(apps) } diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/workspace_sql.rs b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/workspace_sql.rs index dab057526a..20a64f3427 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/workspace_sql.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/workspace_sql.rs @@ -29,8 +29,8 @@ impl WorkspaceTableSql { } pub(crate) fn read_workspaces( - workspace_id: Option, user_id: &str, + workspace_id: Option, conn: &SqliteConnection, ) -> Result, FlowyError> { let mut filter = dsl::workspace_table diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs index 03fc8e6706..4537e8668c 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs @@ -1,4 +1,10 @@ -use crate::services::persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}; +use crate::services::persistence::{ + AppChangeset, + FolderPersistenceTransaction, + ViewChangeset, + WorkspaceChangeset, + FOLDER_ID, +}; use flowy_collaboration::{ entities::revision::Revision, folder::{FolderChange, FolderPad}, @@ -14,8 +20,6 @@ use lib_sqlite::ConnectionPool; use parking_lot::RwLock; use std::sync::Arc; -const FOLDER_ID: &str = "flowy_folder"; - pub struct FolderEditor { user_id: String, folder_pad: Arc>, diff --git a/frontend/rust-lib/flowy-core/src/services/view/controller.rs b/frontend/rust-lib/flowy-core/src/services/view/controller.rs index c2f62d597f..a433cd9f9e 100644 --- a/frontend/rust-lib/flowy-core/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/view/controller.rs @@ -83,6 +83,27 @@ impl ViewController { Ok(view) } + #[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)] + pub(crate) async fn create_view_document_content( + &self, + view_id: &str, + view_data: String, + ) -> Result<(), FlowyError> { + if view_data.is_empty() { + return Err(FlowyError::internal().context("The content of the view should not be empty")); + } + + let delta_data = Bytes::from(view_data); + let user_id = self.user.user_id()?; + let repeated_revision: RepeatedRevision = Revision::initial_revision(&user_id, view_id, delta_data).into(); + let _ = self + .document_ctx + .controller + .save_document(view_id, repeated_revision) + .await?; + Ok(()) + } + pub(crate) async fn create_view_on_local(&self, view: View) -> Result<(), FlowyError> { let trash_controller = self.trash_controller.clone(); self.persistence.begin_transaction(|transaction| { diff --git a/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs b/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs index f5ef2642a3..6a821e5a36 100644 --- a/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs @@ -39,10 +39,6 @@ impl WorkspaceController { params: CreateWorkspaceParams, ) -> Result { let workspace = self.create_workspace_on_server(params.clone()).await?; - self.create_workspace_on_local(workspace).await - } - - pub(crate) async fn create_workspace_on_local(&self, workspace: Workspace) -> Result { let user_id = self.user.user_id()?; let token = self.user.token()?; let workspaces = self.persistence.begin_transaction(|transaction| { @@ -184,7 +180,7 @@ impl WorkspaceController { const CURRENT_WORKSPACE_ID: &str = "current_workspace_id"; -fn set_current_workspace(workspace_id: &str) { KV::set_str(CURRENT_WORKSPACE_ID, workspace_id.to_owned()); } +pub fn set_current_workspace(workspace_id: &str) { KV::set_str(CURRENT_WORKSPACE_ID, workspace_id.to_owned()); } pub fn get_current_workspace() -> Result { match KV::get_str(CURRENT_WORKSPACE_ID) { diff --git a/frontend/rust-lib/flowy-core/tests/workspace/app_test.rs b/frontend/rust-lib/flowy-core/tests/workspace/app_test.rs deleted file mode 100644 index 442ca307a1..0000000000 --- a/frontend/rust-lib/flowy-core/tests/workspace/app_test.rs +++ /dev/null @@ -1,78 +0,0 @@ -use flowy_core::entities::{ - app::QueryAppRequest, - trash::{TrashId, TrashType}, - view::*, -}; -use flowy_test::helper::*; - -#[tokio::test] -#[should_panic] -async fn app_delete() { - let test = AppTest::new().await; - delete_app(&test.sdk, &test.app.id).await; - let query = QueryAppRequest { - app_ids: vec![test.app.id.clone()], - }; - let _ = read_app(&test.sdk, query).await; -} - -#[tokio::test] -async fn app_delete_then_putback() { - let test = AppTest::new().await; - delete_app(&test.sdk, &test.app.id).await; - putback_trash( - &test.sdk, - TrashId { - id: test.app.id.clone(), - ty: TrashType::App, - }, - ) - .await; - - let query = QueryAppRequest { - app_ids: vec![test.app.id.clone()], - }; - let app = read_app(&test.sdk, query).await; - assert_eq!(&app, &test.app); -} - -#[tokio::test] -async fn app_read() { - let test = AppTest::new().await; - let query = QueryAppRequest { - app_ids: vec![test.app.id.clone()], - }; - let app_from_db = read_app(&test.sdk, query).await; - assert_eq!(app_from_db, test.app); -} - -#[tokio::test] -async fn app_create_with_view() { - let test = AppTest::new().await; - let request_a = CreateViewRequest { - belong_to_id: test.app.id.clone(), - name: "View A".to_string(), - desc: "".to_string(), - thumbnail: Some("http://1.png".to_string()), - view_type: ViewType::Doc, - }; - - let request_b = CreateViewRequest { - belong_to_id: test.app.id.clone(), - name: "View B".to_string(), - desc: "".to_string(), - thumbnail: Some("http://1.png".to_string()), - view_type: ViewType::Doc, - }; - - let view_a = create_view_with_request(&test.sdk, request_a).await; - let view_b = create_view_with_request(&test.sdk, request_b).await; - - let query = QueryAppRequest { - app_ids: vec![test.app.id.clone()], - }; - let view_from_db = read_app(&test.sdk, query).await; - - assert_eq!(view_from_db.belongings[0], view_a); - assert_eq!(view_from_db.belongings[1], view_b); -} diff --git a/frontend/rust-lib/flowy-core/tests/workspace/folder_test.rs b/frontend/rust-lib/flowy-core/tests/workspace/folder_test.rs new file mode 100644 index 0000000000..600a2ceee8 --- /dev/null +++ b/frontend/rust-lib/flowy-core/tests/workspace/folder_test.rs @@ -0,0 +1,244 @@ +use flowy_core::{ + entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest}, + event::WorkspaceEvent::*, + prelude::*, +}; +use flowy_test::{event_builder::*, helper::*, FlowySDKTest}; + +#[tokio::test] +async fn workspace_read_all() { + let test = WorkspaceTest::new().await; + let workspace = read_workspace(&test.sdk, QueryWorkspaceRequest::new(None)).await; + assert_eq!(workspace.len(), 2); +} + +#[tokio::test] +async fn workspace_read() { + let test = WorkspaceTest::new().await; + let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone())); + let workspace_from_db = read_workspace(&test.sdk, request) + .await + .drain(..1) + .collect::>() + .pop() + .unwrap(); + assert_eq!(test.workspace, workspace_from_db); +} + +#[tokio::test] +async fn workspace_create_with_apps() { + let test = WorkspaceTest::new().await; + let app = create_app(&test.sdk, "App A", "AppFlowy GitHub Project", &test.workspace.id).await; + let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone())); + let workspace_from_db = read_workspace(&test.sdk, request) + .await + .drain(..1) + .collect::>() + .pop() + .unwrap(); + assert_eq!(&app, workspace_from_db.apps.first_or_crash()); +} + +#[tokio::test] +async fn workspace_create_with_invalid_name() { + for (name, code) in invalid_workspace_name_test_case() { + let sdk = FlowySDKTest::default(); + let request = CreateWorkspaceRequest { + name, + desc: "".to_owned(), + }; + assert_eq!( + CoreModuleEventBuilder::new(sdk) + .event(CreateWorkspace) + .request(request) + .async_send() + .await + .error() + .code, + code.value() + ) + } +} + +#[tokio::test] +async fn workspace_update_with_invalid_name() { + let sdk = FlowySDKTest::default(); + for (name, code) in invalid_workspace_name_test_case() { + let request = CreateWorkspaceRequest { + name, + desc: "".to_owned(), + }; + assert_eq!( + CoreModuleEventBuilder::new(sdk.clone()) + .event(CreateWorkspace) + .request(request) + .async_send() + .await + .error() + .code, + code.value() + ) + } +} + +#[tokio::test] +#[should_panic] +async fn app_delete() { + let test = AppTest::new().await; + delete_app(&test.sdk, &test.app.id).await; + let query = QueryAppRequest { + app_ids: vec![test.app.id.clone()], + }; + let _ = read_app(&test.sdk, query).await; +} + +#[tokio::test] +async fn app_delete_then_putback() { + let test = AppTest::new().await; + delete_app(&test.sdk, &test.app.id).await; + putback_trash( + &test.sdk, + TrashId { + id: test.app.id.clone(), + ty: TrashType::App, + }, + ) + .await; + + let query = QueryAppRequest { + app_ids: vec![test.app.id.clone()], + }; + let app = read_app(&test.sdk, query).await; + assert_eq!(&app, &test.app); +} + +#[tokio::test] +async fn app_read() { + let test = AppTest::new().await; + let query = QueryAppRequest { + app_ids: vec![test.app.id.clone()], + }; + let app_from_db = read_app(&test.sdk, query).await; + assert_eq!(app_from_db, test.app); +} + +#[tokio::test] +async fn app_create_with_view() { + let test = AppTest::new().await; + let request_a = CreateViewRequest { + belong_to_id: test.app.id.clone(), + name: "View A".to_string(), + desc: "".to_string(), + thumbnail: Some("http://1.png".to_string()), + view_type: ViewType::Doc, + }; + + let request_b = CreateViewRequest { + belong_to_id: test.app.id.clone(), + name: "View B".to_string(), + desc: "".to_string(), + thumbnail: Some("http://1.png".to_string()), + view_type: ViewType::Doc, + }; + + let view_a = create_view_with_request(&test.sdk, request_a).await; + let view_b = create_view_with_request(&test.sdk, request_b).await; + + let query = QueryAppRequest { + app_ids: vec![test.app.id.clone()], + }; + let view_from_db = read_app(&test.sdk, query).await; + + assert_eq!(view_from_db.belongings[0], view_a); + assert_eq!(view_from_db.belongings[1], view_b); +} + +#[tokio::test] +#[should_panic] +async fn view_delete() { + let test = FlowySDKTest::default(); + let _ = test.init_user().await; + + let test = ViewTest::new(&test).await; + test.delete_views(vec![test.view.id.clone()]).await; + let query = QueryViewRequest { + view_ids: vec![test.view.id.clone()], + }; + let _ = read_view(&test.sdk, query).await; +} + +#[tokio::test] +async fn view_delete_then_putback() { + let test = FlowySDKTest::default(); + let _ = test.init_user().await; + + let test = ViewTest::new(&test).await; + test.delete_views(vec![test.view.id.clone()]).await; + putback_trash( + &test.sdk, + TrashId { + id: test.view.id.clone(), + ty: TrashType::View, + }, + ) + .await; + + let query = QueryViewRequest { + view_ids: vec![test.view.id.clone()], + }; + let view = read_view(&test.sdk, query).await; + assert_eq!(&view, &test.view); +} + +#[tokio::test] +async fn view_delete_all() { + let test = FlowySDKTest::default(); + let _ = test.init_user().await; + + let test = ViewTest::new(&test).await; + let view1 = test.view.clone(); + let view2 = create_view(&test.sdk, &test.app.id).await; + let view3 = create_view(&test.sdk, &test.app.id).await; + let view_ids = vec![view1.id.clone(), view2.id.clone(), view3.id.clone()]; + + let query = QueryAppRequest { + app_ids: vec![test.app.id.clone()], + }; + let app = read_app(&test.sdk, query.clone()).await; + assert_eq!(app.belongings.len(), view_ids.len()); + test.delete_views(view_ids.clone()).await; + + assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0); + assert_eq!(read_trash(&test.sdk).await.len(), view_ids.len()); +} + +#[tokio::test] +async fn view_delete_all_permanent() { + let test = FlowySDKTest::default(); + let _ = test.init_user().await; + + let test = ViewTest::new(&test).await; + let view1 = test.view.clone(); + let view2 = create_view(&test.sdk, &test.app.id).await; + + let view_ids = vec![view1.id.clone(), view2.id.clone()]; + test.delete_views_permanent(view_ids).await; + + let query = QueryAppRequest { + app_ids: vec![test.app.id.clone()], + }; + assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0); + assert_eq!(read_trash(&test.sdk).await.len(), 0); +} + +#[tokio::test] +async fn view_open_doc() { + let test = FlowySDKTest::default(); + let _ = test.init_user().await; + + let test = ViewTest::new(&test).await; + let request = QueryViewRequest { + view_ids: vec![test.view.id.clone()], + }; + let _ = open_view(&test.sdk, request).await; +} diff --git a/frontend/rust-lib/flowy-core/tests/workspace/main.rs b/frontend/rust-lib/flowy-core/tests/workspace/main.rs index 58be783efb..8433fea058 100644 --- a/frontend/rust-lib/flowy-core/tests/workspace/main.rs +++ b/frontend/rust-lib/flowy-core/tests/workspace/main.rs @@ -1,4 +1 @@ -mod app_test; -// mod helper; -mod view_test; -mod workspace_test; +mod folder_test; diff --git a/frontend/rust-lib/flowy-core/tests/workspace/view_test.rs b/frontend/rust-lib/flowy-core/tests/workspace/view_test.rs deleted file mode 100644 index 0bbf507061..0000000000 --- a/frontend/rust-lib/flowy-core/tests/workspace/view_test.rs +++ /dev/null @@ -1,96 +0,0 @@ -use flowy_core::entities::{ - app::QueryAppRequest, - trash::{TrashId, TrashType}, - view::*, -}; -use flowy_test::{helper::*, FlowySDKTest}; - -#[tokio::test] -#[should_panic] -async fn view_delete() { - let test = FlowySDKTest::default(); - let _ = test.init_user().await; - - let test = ViewTest::new(&test).await; - test.delete_views(vec![test.view.id.clone()]).await; - let query = QueryViewRequest { - view_ids: vec![test.view.id.clone()], - }; - let _ = read_view(&test.sdk, query).await; -} - -#[tokio::test] -async fn view_delete_then_putback() { - let test = FlowySDKTest::default(); - let _ = test.init_user().await; - - let test = ViewTest::new(&test).await; - test.delete_views(vec![test.view.id.clone()]).await; - putback_trash( - &test.sdk, - TrashId { - id: test.view.id.clone(), - ty: TrashType::View, - }, - ) - .await; - - let query = QueryViewRequest { - view_ids: vec![test.view.id.clone()], - }; - let view = read_view(&test.sdk, query).await; - assert_eq!(&view, &test.view); -} - -#[tokio::test] -async fn view_delete_all() { - let test = FlowySDKTest::default(); - let _ = test.init_user().await; - - let test = ViewTest::new(&test).await; - let view1 = test.view.clone(); - let view2 = create_view(&test.sdk, &test.app.id).await; - let view3 = create_view(&test.sdk, &test.app.id).await; - let view_ids = vec![view1.id.clone(), view2.id.clone(), view3.id.clone()]; - - let query = QueryAppRequest { - app_ids: vec![test.app.id.clone()], - }; - let app = read_app(&test.sdk, query.clone()).await; - assert_eq!(app.belongings.len(), view_ids.len()); - test.delete_views(view_ids.clone()).await; - - assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0); - assert_eq!(read_trash(&test.sdk).await.len(), view_ids.len()); -} - -#[tokio::test] -async fn view_delete_all_permanent() { - let test = FlowySDKTest::default(); - let _ = test.init_user().await; - - let test = ViewTest::new(&test).await; - let view1 = test.view.clone(); - let view2 = create_view(&test.sdk, &test.app.id).await; - - let view_ids = vec![view1.id.clone(), view2.id.clone()]; - test.delete_views_permanent(view_ids).await; - - let query = QueryAppRequest { - app_ids: vec![test.app.id.clone()], - }; - assert_eq!(read_app(&test.sdk, query).await.belongings.len(), 0); - assert_eq!(read_trash(&test.sdk).await.len(), 0); -} - -#[tokio::test] -async fn view_open_doc() { - let test = FlowySDKTest::default(); - let _ = test.init_user().await; - - let test = ViewTest::new(&test).await; - let request = QueryViewRequest { - view_ids: vec![test.view.id.clone()], - }; - let _ = open_view(&test.sdk, request).await; -} diff --git a/frontend/rust-lib/flowy-core/tests/workspace/workspace_test.rs b/frontend/rust-lib/flowy-core/tests/workspace/workspace_test.rs deleted file mode 100644 index 913a9556d2..0000000000 --- a/frontend/rust-lib/flowy-core/tests/workspace/workspace_test.rs +++ /dev/null @@ -1,84 +0,0 @@ -use flowy_core::{ - entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest}, - event::WorkspaceEvent::*, - prelude::*, -}; -use flowy_test::{event_builder::*, helper::*, FlowySDKTest}; - -#[tokio::test] -async fn workspace_read_all() { - let test = WorkspaceTest::new().await; - let workspace = read_workspace(&test.sdk, QueryWorkspaceRequest::new(None)).await; - assert_eq!(workspace.len(), 2); -} - -#[tokio::test] -async fn workspace_read() { - let test = WorkspaceTest::new().await; - let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone())); - let workspace_from_db = read_workspace(&test.sdk, request) - .await - .drain(..1) - .collect::>() - .pop() - .unwrap(); - assert_eq!(test.workspace, workspace_from_db); -} - -#[tokio::test] -async fn workspace_create_with_apps() { - let test = WorkspaceTest::new().await; - let app = create_app(&test.sdk, "App A", "AppFlowy GitHub Project", &test.workspace.id).await; - let request = QueryWorkspaceRequest::new(Some(test.workspace.id.clone())); - let workspace_from_db = read_workspace(&test.sdk, request) - .await - .drain(..1) - .collect::>() - .pop() - .unwrap(); - assert_eq!(&app, workspace_from_db.apps.first_or_crash()); -} - -#[tokio::test] -async fn workspace_create_with_invalid_name() { - for (name, code) in invalid_workspace_name_test_case() { - let sdk = FlowySDKTest::default(); - let request = CreateWorkspaceRequest { - name, - desc: "".to_owned(), - }; - assert_eq!( - CoreModuleEventBuilder::new(sdk) - .event(CreateWorkspace) - .request(request) - .async_send() - .await - .error() - .code, - code.value() - ) - } -} - -#[tokio::test] -async fn workspace_update_with_invalid_name() { - let sdk = FlowySDKTest::default(); - for (name, code) in invalid_workspace_name_test_case() { - let request = CreateWorkspaceRequest { - name, - desc: "".to_owned(), - }; - assert_eq!( - CoreModuleEventBuilder::new(sdk.clone()) - .event(CreateWorkspace) - .request(request) - .async_send() - .await - .error() - .code, - code.value() - ) - } -} - -// TODO 1) delete workspace, but can't delete the last workspace diff --git a/frontend/rust-lib/flowy-sdk/src/lib.rs b/frontend/rust-lib/flowy-sdk/src/lib.rs index 475010c939..1c6f694c2e 100644 --- a/frontend/rust-lib/flowy-sdk/src/lib.rs +++ b/frontend/rust-lib/flowy-sdk/src/lib.rs @@ -177,7 +177,7 @@ async fn _listen_user_status( match status { UserStatus::Login { token, user_id } => { tracing::trace!("User did login"); - let _ = folder_manager.initialize(&token).await?; + let _ = folder_manager.initialize(&user_id).await?; let _ = ws_conn.start(token, user_id).await?; }, UserStatus::Logout { .. } => { @@ -192,7 +192,9 @@ async fn _listen_user_status( }, UserStatus::SignUp { profile, ret } => { tracing::trace!("User did sign up"); - let _ = folder_manager.initialize_with_new_user(&profile.token).await?; + let _ = folder_manager + .initialize_with_new_user(&profile.id, &profile.token) + .await?; let _ = ws_conn.start(profile.token.clone(), profile.id.clone()).await?; let _ = ret.send(()); }, diff --git a/frontend/rust-lib/flowy-sync/src/cache/mod.rs b/frontend/rust-lib/flowy-sync/src/cache/mod.rs index 6e9a00ed6d..36a916b001 100644 --- a/frontend/rust-lib/flowy-sync/src/cache/mod.rs +++ b/frontend/rust-lib/flowy-sync/src/cache/mod.rs @@ -24,6 +24,13 @@ pub struct RevisionCache { latest_rev_id: AtomicI64, } +pub fn mk_revision_disk_cache( + user_id: &str, + pool: Arc, +) -> Arc> { + Arc::new(SQLitePersistence::new(user_id, pool)) +} + impl RevisionCache { pub fn new(user_id: &str, object_id: &str, pool: Arc) -> RevisionCache { let disk_cache = Arc::new(SQLitePersistence::new(user_id, pool)); diff --git a/frontend/rust-lib/flowy-test/src/lib.rs b/frontend/rust-lib/flowy-test/src/lib.rs index 2ba8a20828..d3cd6c5cf2 100644 --- a/frontend/rust-lib/flowy-test/src/lib.rs +++ b/frontend/rust-lib/flowy-test/src/lib.rs @@ -4,8 +4,9 @@ pub mod helper; use crate::helper::*; use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration}; use flowy_sdk::{FlowySDK, FlowySDKConfig}; -use flowy_user::entities::UserProfile; +use flowy_user::{entities::UserProfile, services::database::UserDB}; use lib_infra::uuid_string; +use std::sync::Arc; pub mod prelude { pub use crate::{event_builder::*, helper::*, *}; @@ -51,3 +52,15 @@ impl FlowySDKTest { context.user_profile } } + +pub struct MigrationTest { + pub db: UserDB, +} + +impl MigrationTest { + pub fn new() -> Self { + let dir = root_dir(); + let db = UserDB::new(&dir); + Self { db } + } +} diff --git a/frontend/rust-lib/flowy-user/src/services/database.rs b/frontend/rust-lib/flowy-user/src/services/database.rs index 896bc7beb0..a57fd3afc0 100644 --- a/frontend/rust-lib/flowy-user/src/services/database.rs +++ b/frontend/rust-lib/flowy-user/src/services/database.rs @@ -11,12 +11,12 @@ lazy_static! { static ref DB: RwLock> = RwLock::new(None); } -pub(crate) struct UserDB { +pub struct UserDB { db_dir: String, } impl UserDB { - pub(crate) fn new(db_dir: &str) -> Self { + pub fn new(db_dir: &str) -> Self { Self { db_dir: db_dir.to_owned(), } diff --git a/frontend/rust-lib/flowy-user/src/services/mod.rs b/frontend/rust-lib/flowy-user/src/services/mod.rs index c81b7456a8..7ad18349c9 100644 --- a/frontend/rust-lib/flowy-user/src/services/mod.rs +++ b/frontend/rust-lib/flowy-user/src/services/mod.rs @@ -1,4 +1,4 @@ -mod database; +pub mod database; pub mod notifier; mod user_session; pub use user_session::*; diff --git a/shared-lib/flowy-collaboration/src/folder/folder_pad.rs b/shared-lib/flowy-collaboration/src/folder/folder_pad.rs index 8c95b10cdf..c555ef652e 100644 --- a/shared-lib/flowy-collaboration/src/folder/folder_pad.rs +++ b/shared-lib/flowy-collaboration/src/folder/folder_pad.rs @@ -39,6 +39,15 @@ pub struct FolderChange { } impl FolderPad { + pub fn new(workspaces: Vec, trash: Vec) -> CollaborateResult { + let mut pad = FolderPad::default(); + pad.workspaces = workspaces.into_iter().map(Arc::new).collect::>(); + pad.trash = trash.into_iter().map(Arc::new).collect::>(); + let json = pad.to_json()?; + pad.root = PlainDeltaBuilder::new().insert(&json).build(); + Ok(pad) + } + pub fn from_revisions(revisions: Vec) -> CollaborateResult { let mut folder_delta = PlainDelta::new(); for revision in revisions { @@ -65,6 +74,8 @@ impl FolderPad { Ok(folder) } + pub fn delta(&self) -> &PlainDelta { &self.root } + pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult> { let workspace = Arc::new(workspace); if self.workspaces.contains(&workspace) { diff --git a/shared-lib/flowy-collaboration/src/server_document/document_pad.rs b/shared-lib/flowy-collaboration/src/server_document/document_pad.rs index 40b6c42d73..a67f49a227 100644 --- a/shared-lib/flowy-collaboration/src/server_document/document_pad.rs +++ b/shared-lib/flowy-collaboration/src/server_document/document_pad.rs @@ -23,7 +23,7 @@ impl RevisionSyncObject for ServerDocument { fn id(&self) -> &str { &self.doc_id } fn compose(&mut self, other: &RichTextDelta) -> Result<(), CollaborateError> { - tracing::trace!("{} compose {}", &self.delta.to_json(), other.to_json()); + // tracing::trace!("{} compose {}", &self.delta.to_json(), other.to_json()); let new_delta = self.delta.compose(other)?; self.delta = new_delta; Ok(())