mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-25 07:07:32 -04:00
add folder migration & add folder unit test
This commit is contained in:
parent
44ff74a37c
commit
324dc53e5f
22 changed files with 479 additions and 341 deletions
|
@ -44,8 +44,8 @@ crossbeam-utils = "0.8"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
flowy-test = { path = "../flowy-test" }
|
|
||||||
serial_test = "0.5.1"
|
serial_test = "0.5.1"
|
||||||
|
flowy-test = { path = "../flowy-test" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use flowy_collaboration::client_document::default::{initial_delta, initial_read_me};
|
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_document::context::DocumentContext;
|
||||||
use flowy_sync::RevisionWebSocket;
|
use flowy_sync::RevisionWebSocket;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use futures_core::future::BoxFuture;
|
use flowy_collaboration::folder::FolderPad;
|
||||||
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
@ -16,11 +15,18 @@ use crate::{
|
||||||
entities::workspace::RepeatedWorkspace,
|
entities::workspace::RepeatedWorkspace,
|
||||||
errors::FlowyResult,
|
errors::FlowyResult,
|
||||||
module::{FolderCouldServiceV1, WorkspaceUser},
|
module::{FolderCouldServiceV1, WorkspaceUser},
|
||||||
services::{persistence::FolderPersistence, AppController, TrashController, ViewController, WorkspaceController},
|
services::{
|
||||||
|
persistence::FolderPersistence,
|
||||||
|
set_current_workspace,
|
||||||
|
AppController,
|
||||||
|
TrashController,
|
||||||
|
ViewController,
|
||||||
|
WorkspaceController,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref INIT_WORKSPACE: RwLock<HashMap<String, bool>> = RwLock::new(HashMap::new());
|
static ref INIT_FOLDER_FLAG: RwLock<HashMap<String, bool>> = RwLock::new(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FolderManager {
|
pub struct FolderManager {
|
||||||
|
@ -43,7 +49,7 @@ impl FolderManager {
|
||||||
ws_sender: Arc<dyn RevisionWebSocket>,
|
ws_sender: Arc<dyn RevisionWebSocket>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if let Ok(token) = user.token() {
|
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(
|
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 did_receive_ws_data(&self, _data: Bytes) {}
|
||||||
|
|
||||||
pub async fn initialize(&self, token: &str) -> FlowyResult<()> {
|
pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> {
|
||||||
self.initialize_with_fn(token, || Box::pin(async { Ok(()) })).await?;
|
if let Some(is_init) = INIT_FOLDER_FLAG.read().get(user_id) {
|
||||||
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) {
|
|
||||||
if *is_init {
|
if *is_init {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
INIT_WORKSPACE.write().insert(token.to_owned(), true);
|
let _ = self.persistence.initialize(user_id).await?;
|
||||||
|
|
||||||
self.persistence.initialize().await?;
|
|
||||||
f().await?;
|
|
||||||
let _ = self.app_controller.initialize()?;
|
let _ = self.app_controller.initialize()?;
|
||||||
let _ = self.view_controller.initialize()?;
|
let _ = self.view_controller.initialize()?;
|
||||||
|
INIT_FOLDER_FLAG.write().insert(user_id.to_owned(), true);
|
||||||
Ok(())
|
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<FolderPersistence>,
|
||||||
|
view_controller: Arc<ViewController>,
|
||||||
|
) -> FlowyResult<()> {
|
||||||
log::debug!("Create user default workspace");
|
log::debug!("Create user default workspace");
|
||||||
let time = Utc::now();
|
let time = Utc::now();
|
||||||
let workspace = user_default::create_default_workspace(time);
|
let workspace = user_default::create_default_workspace(time);
|
||||||
let apps = workspace.apps.clone().into_inner();
|
set_current_workspace(&workspace.id);
|
||||||
let cloned_workspace = workspace.clone();
|
for app in workspace.apps.iter() {
|
||||||
|
for (index, view) in app.belongings.iter().enumerate() {
|
||||||
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() {
|
|
||||||
let view_data = if index == 0 {
|
let view_data = if index == 0 {
|
||||||
initial_read_me().to_json()
|
initial_read_me().to_json()
|
||||||
} else {
|
} else {
|
||||||
initial_delta().to_json()
|
initial_delta().to_json()
|
||||||
};
|
};
|
||||||
self.view_controller.set_latest_view(&view);
|
view_controller.set_latest_view(&view);
|
||||||
let params = CreateViewParams {
|
let _ = view_controller
|
||||||
belong_to_id: app_id.clone(),
|
.create_view_document_content(&view.id, view_data)
|
||||||
name: view.name,
|
.await?;
|
||||||
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?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let folder = FolderPad::new(vec![workspace.clone()], vec![])?;
|
||||||
let token = self.user.token()?;
|
let _ = persistence.save_folder(user_id, folder).await?;
|
||||||
let repeated_workspace = RepeatedWorkspace {
|
let repeated_workspace = RepeatedWorkspace { items: vec![workspace] };
|
||||||
items: vec![cloned_workspace],
|
send_dart_notification(token, WorkspaceNotification::UserCreateWorkspace)
|
||||||
};
|
|
||||||
|
|
||||||
send_dart_notification(&token, WorkspaceNotification::UserCreateWorkspace)
|
|
||||||
.payload(repeated_workspace)
|
.payload(repeated_workspace)
|
||||||
.send();
|
.send();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -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<dyn WorkspaceDatabase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FolderMigration {
|
||||||
|
pub fn new(user_id: &str, database: Arc<dyn WorkspaceDatabase>) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: user_id.to_owned(),
|
||||||
|
database,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_v1_migration(&self) -> FlowyResult<Option<FolderPad>> {
|
||||||
|
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::<Vec<_>>();
|
||||||
|
|
||||||
|
for workspace in workspaces.iter_mut() {
|
||||||
|
let mut apps = AppTableSql::read_workspace_apps(&workspace.id, conn)?
|
||||||
|
.into_iter()
|
||||||
|
.map(App::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for app in apps.iter_mut() {
|
||||||
|
let views = ViewTableSql::read_views(&app.id, conn)?
|
||||||
|
.into_iter()
|
||||||
|
.map(View::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,18 @@
|
||||||
|
mod migration;
|
||||||
pub mod version_1;
|
pub mod version_1;
|
||||||
mod version_2;
|
mod version_2;
|
||||||
|
|
||||||
|
use flowy_collaboration::{
|
||||||
|
entities::revision::{Revision, RevisionState},
|
||||||
|
folder::FolderPad,
|
||||||
|
};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*};
|
pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
module::{WorkspaceDatabase, WorkspaceUser},
|
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::{
|
use flowy_core_data_model::entities::{
|
||||||
app::App,
|
app::App,
|
||||||
|
@ -17,6 +22,9 @@ use flowy_core_data_model::entities::{
|
||||||
workspace::Workspace,
|
workspace::Workspace,
|
||||||
};
|
};
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
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 {
|
pub trait FolderPersistenceTransaction {
|
||||||
fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()>;
|
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)]
|
#[allow(dead_code)]
|
||||||
pub fn begin_transaction2<F, O>(&self, f: F) -> FlowyResult<O>
|
pub fn begin_transaction_v_1<F, O>(&self, f: F) -> FlowyResult<O>
|
||||||
where
|
where
|
||||||
F: for<'a> FnOnce(Box<dyn FolderPersistenceTransaction + 'a>) -> FlowyResult<O>,
|
F: for<'a> FnOnce(Box<dyn FolderPersistenceTransaction + 'a>) -> FlowyResult<O>,
|
||||||
{
|
{
|
||||||
|
@ -93,7 +105,13 @@ impl FolderPersistence {
|
||||||
|
|
||||||
pub fn user_did_logout(&self) { *self.folder_editor.write() = None; }
|
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?;
|
let _ = self.init_folder_editor().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -107,4 +125,20 @@ impl FolderPersistence {
|
||||||
*self.folder_editor.write() = Some(editor.clone());
|
*self.folder_editor.write() = Some(editor.clone());
|
||||||
Ok(editor)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,10 @@ impl AppTableSql {
|
||||||
|
|
||||||
pub(crate) fn read_workspace_apps(
|
pub(crate) fn read_workspace_apps(
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
is_trash: bool,
|
|
||||||
conn: &SqliteConnection,
|
conn: &SqliteConnection,
|
||||||
) -> Result<Vec<AppTable>, FlowyError> {
|
) -> Result<Vec<AppTable>, FlowyError> {
|
||||||
let app_table = dsl::app_table
|
let app_table = dsl::app_table
|
||||||
.filter(app_table::workspace_id.eq(workspace_id))
|
.filter(app_table::workspace_id.eq(workspace_id))
|
||||||
.filter(app_table::is_trash.eq(is_trash))
|
|
||||||
.order(app_table::create_time.asc())
|
.order(app_table::create_time.asc())
|
||||||
.load::<AppTable>(conn)?;
|
.load::<AppTable>(conn)?;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
|
fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
|
||||||
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::<Vec<_>>();
|
let workspaces = tables.into_iter().map(Workspace::from).collect::<Vec<_>>();
|
||||||
Ok(workspaces)
|
Ok(workspaces)
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
|
fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
|
||||||
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::<Vec<_>>();
|
let apps = tables.into_iter().map(App::from).collect::<Vec<_>>();
|
||||||
Ok(apps)
|
Ok(apps)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,8 @@ impl WorkspaceTableSql {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn read_workspaces(
|
pub(crate) fn read_workspaces(
|
||||||
workspace_id: Option<String>,
|
|
||||||
user_id: &str,
|
user_id: &str,
|
||||||
|
workspace_id: Option<String>,
|
||||||
conn: &SqliteConnection,
|
conn: &SqliteConnection,
|
||||||
) -> Result<Vec<WorkspaceTable>, FlowyError> {
|
) -> Result<Vec<WorkspaceTable>, FlowyError> {
|
||||||
let mut filter = dsl::workspace_table
|
let mut filter = dsl::workspace_table
|
||||||
|
|
|
@ -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::{
|
use flowy_collaboration::{
|
||||||
entities::revision::Revision,
|
entities::revision::Revision,
|
||||||
folder::{FolderChange, FolderPad},
|
folder::{FolderChange, FolderPad},
|
||||||
|
@ -14,8 +20,6 @@ use lib_sqlite::ConnectionPool;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
const FOLDER_ID: &str = "flowy_folder";
|
|
||||||
|
|
||||||
pub struct FolderEditor {
|
pub struct FolderEditor {
|
||||||
user_id: String,
|
user_id: String,
|
||||||
folder_pad: Arc<RwLock<FolderPad>>,
|
folder_pad: Arc<RwLock<FolderPad>>,
|
||||||
|
|
|
@ -83,6 +83,27 @@ impl ViewController {
|
||||||
Ok(view)
|
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> {
|
pub(crate) async fn create_view_on_local(&self, view: View) -> Result<(), FlowyError> {
|
||||||
let trash_controller = self.trash_controller.clone();
|
let trash_controller = self.trash_controller.clone();
|
||||||
self.persistence.begin_transaction(|transaction| {
|
self.persistence.begin_transaction(|transaction| {
|
||||||
|
|
|
@ -39,10 +39,6 @@ impl WorkspaceController {
|
||||||
params: CreateWorkspaceParams,
|
params: CreateWorkspaceParams,
|
||||||
) -> Result<Workspace, FlowyError> {
|
) -> Result<Workspace, FlowyError> {
|
||||||
let workspace = self.create_workspace_on_server(params.clone()).await?;
|
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<Workspace, FlowyError> {
|
|
||||||
let user_id = self.user.user_id()?;
|
let user_id = self.user.user_id()?;
|
||||||
let token = self.user.token()?;
|
let token = self.user.token()?;
|
||||||
let workspaces = self.persistence.begin_transaction(|transaction| {
|
let workspaces = self.persistence.begin_transaction(|transaction| {
|
||||||
|
@ -184,7 +180,7 @@ impl WorkspaceController {
|
||||||
|
|
||||||
const CURRENT_WORKSPACE_ID: &str = "current_workspace_id";
|
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<String, FlowyError> {
|
pub fn get_current_workspace() -> Result<String, FlowyError> {
|
||||||
match KV::get_str(CURRENT_WORKSPACE_ID) {
|
match KV::get_str(CURRENT_WORKSPACE_ID) {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
244
frontend/rust-lib/flowy-core/tests/workspace/folder_test.rs
Normal file
244
frontend/rust-lib/flowy-core/tests/workspace/folder_test.rs
Normal file
|
@ -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::<Vec<Workspace>>()
|
||||||
|
.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::<Vec<Workspace>>()
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -1,4 +1 @@
|
||||||
mod app_test;
|
mod folder_test;
|
||||||
// mod helper;
|
|
||||||
mod view_test;
|
|
||||||
mod workspace_test;
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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::<Vec<Workspace>>()
|
|
||||||
.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::<Vec<Workspace>>()
|
|
||||||
.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
|
|
|
@ -177,7 +177,7 @@ async fn _listen_user_status(
|
||||||
match status {
|
match status {
|
||||||
UserStatus::Login { token, user_id } => {
|
UserStatus::Login { token, user_id } => {
|
||||||
tracing::trace!("User did login");
|
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?;
|
let _ = ws_conn.start(token, user_id).await?;
|
||||||
},
|
},
|
||||||
UserStatus::Logout { .. } => {
|
UserStatus::Logout { .. } => {
|
||||||
|
@ -192,7 +192,9 @@ async fn _listen_user_status(
|
||||||
},
|
},
|
||||||
UserStatus::SignUp { profile, ret } => {
|
UserStatus::SignUp { profile, ret } => {
|
||||||
tracing::trace!("User did sign up");
|
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 _ = ws_conn.start(profile.token.clone(), profile.id.clone()).await?;
|
||||||
let _ = ret.send(());
|
let _ = ret.send(());
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,6 +24,13 @@ pub struct RevisionCache {
|
||||||
latest_rev_id: AtomicI64,
|
latest_rev_id: AtomicI64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mk_revision_disk_cache(
|
||||||
|
user_id: &str,
|
||||||
|
pool: Arc<ConnectionPool>,
|
||||||
|
) -> Arc<dyn RevisionDiskCache<Error = FlowyError>> {
|
||||||
|
Arc::new(SQLitePersistence::new(user_id, pool))
|
||||||
|
}
|
||||||
|
|
||||||
impl RevisionCache {
|
impl RevisionCache {
|
||||||
pub fn new(user_id: &str, object_id: &str, pool: Arc<ConnectionPool>) -> RevisionCache {
|
pub fn new(user_id: &str, object_id: &str, pool: Arc<ConnectionPool>) -> RevisionCache {
|
||||||
let disk_cache = Arc::new(SQLitePersistence::new(user_id, pool));
|
let disk_cache = Arc::new(SQLitePersistence::new(user_id, pool));
|
||||||
|
|
|
@ -4,8 +4,9 @@ pub mod helper;
|
||||||
use crate::helper::*;
|
use crate::helper::*;
|
||||||
use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration};
|
use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration};
|
||||||
use flowy_sdk::{FlowySDK, FlowySDKConfig};
|
use flowy_sdk::{FlowySDK, FlowySDKConfig};
|
||||||
use flowy_user::entities::UserProfile;
|
use flowy_user::{entities::UserProfile, services::database::UserDB};
|
||||||
use lib_infra::uuid_string;
|
use lib_infra::uuid_string;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::{event_builder::*, helper::*, *};
|
pub use crate::{event_builder::*, helper::*, *};
|
||||||
|
@ -51,3 +52,15 @@ impl FlowySDKTest {
|
||||||
context.user_profile
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ lazy_static! {
|
||||||
static ref DB: RwLock<Option<Database>> = RwLock::new(None);
|
static ref DB: RwLock<Option<Database>> = RwLock::new(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct UserDB {
|
pub struct UserDB {
|
||||||
db_dir: String,
|
db_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserDB {
|
impl UserDB {
|
||||||
pub(crate) fn new(db_dir: &str) -> Self {
|
pub fn new(db_dir: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
db_dir: db_dir.to_owned(),
|
db_dir: db_dir.to_owned(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mod database;
|
pub mod database;
|
||||||
pub mod notifier;
|
pub mod notifier;
|
||||||
mod user_session;
|
mod user_session;
|
||||||
pub use user_session::*;
|
pub use user_session::*;
|
||||||
|
|
|
@ -39,6 +39,15 @@ pub struct FolderChange {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderPad {
|
impl FolderPad {
|
||||||
|
pub fn new(workspaces: Vec<Workspace>, trash: Vec<Trash>) -> CollaborateResult<Self> {
|
||||||
|
let mut pad = FolderPad::default();
|
||||||
|
pad.workspaces = workspaces.into_iter().map(Arc::new).collect::<Vec<_>>();
|
||||||
|
pad.trash = trash.into_iter().map(Arc::new).collect::<Vec<_>>();
|
||||||
|
let json = pad.to_json()?;
|
||||||
|
pad.root = PlainDeltaBuilder::new().insert(&json).build();
|
||||||
|
Ok(pad)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
|
pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
|
||||||
let mut folder_delta = PlainDelta::new();
|
let mut folder_delta = PlainDelta::new();
|
||||||
for revision in revisions {
|
for revision in revisions {
|
||||||
|
@ -65,6 +74,8 @@ impl FolderPad {
|
||||||
Ok(folder)
|
Ok(folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn delta(&self) -> &PlainDelta { &self.root }
|
||||||
|
|
||||||
pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<FolderChange>> {
|
pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<FolderChange>> {
|
||||||
let workspace = Arc::new(workspace);
|
let workspace = Arc::new(workspace);
|
||||||
if self.workspaces.contains(&workspace) {
|
if self.workspaces.contains(&workspace) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ impl RevisionSyncObject<RichTextAttributes> for ServerDocument {
|
||||||
fn id(&self) -> &str { &self.doc_id }
|
fn id(&self) -> &str { &self.doc_id }
|
||||||
|
|
||||||
fn compose(&mut self, other: &RichTextDelta) -> Result<(), CollaborateError> {
|
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)?;
|
let new_delta = self.delta.compose(other)?;
|
||||||
self.delta = new_delta;
|
self.delta = new_delta;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue