appflowy 3 年之前
父節點
當前提交
152cb17701
共有 42 個文件被更改,包括 528 次插入363 次删除
  1. 10 13
      backend/src/services/core/view/controller.rs
  2. 9 6
      backend/src/services/core/view/persistence.rs
  3. 8 2
      backend/src/services/core/view/router.rs
  4. 1 1
      backend/src/services/document/mod.rs
  5. 5 8
      backend/src/services/document/persistence.rs
  6. 3 2
      backend/src/services/document/router.rs
  7. 10 14
      backend/src/services/document/ws_actor.rs
  8. 2 4
      backend/src/services/document/ws_receiver.rs
  9. 32 7
      backend/src/services/kv/kv.rs
  10. 1 3
      backend/src/services/kv/mod.rs
  11. 27 0
      backend/tests/api_test/kv_test.rs
  12. 35 25
      backend/tests/document_test/edit_script.rs
  13. 1 2
      backend/tests/document_test/edit_test.rs
  14. 15 7
      backend/tests/util/helper.rs
  15. 28 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-core-data-model/view_create.pb.dart
  16. 3 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-core-data-model/view_create.pbjson.dart
  17. 21 17
      frontend/rust-lib/flowy-core/src/core/core_context.rs
  18. 6 53
      frontend/rust-lib/flowy-core/src/services/app/controller.rs
  19. 4 4
      frontend/rust-lib/flowy-core/src/services/app/event_handler.rs
  20. 4 4
      frontend/rust-lib/flowy-core/src/services/server/server_api_mock.rs
  21. 1 1
      frontend/rust-lib/flowy-core/src/services/trash/controller.rs
  22. 21 18
      frontend/rust-lib/flowy-core/src/services/view/controller.rs
  23. 5 5
      frontend/rust-lib/flowy-core/src/services/view/event_handler.rs
  24. 2 2
      frontend/rust-lib/flowy-core/src/services/workspace/controller.rs
  25. 2 22
      frontend/rust-lib/flowy-document/src/context.rs
  26. 5 5
      frontend/rust-lib/flowy-document/src/services/controller.rs
  27. 2 2
      frontend/rust-lib/flowy-sdk/src/lib.rs
  28. 1 1
      frontend/rust-lib/flowy-test/src/doc_script.rs
  29. 2 2
      frontend/rust-lib/flowy-test/src/helper.rs
  30. 2 2
      frontend/rust-lib/flowy-test/src/lib.rs
  31. 3 3
      frontend/rust-lib/flowy-user/src/services/server/server_api_mock.rs
  32. 2 2
      frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs
  33. 1 1
      frontend/rust-lib/flowy-virtual-net/src/mock/server.rs
  34. 1 1
      shared-lib/backend-service/src/configuration.rs
  35. 6 3
      shared-lib/flowy-collaboration/src/entities/doc.rs
  36. 0 7
      shared-lib/flowy-collaboration/src/entities/revision.rs
  37. 37 15
      shared-lib/flowy-collaboration/src/sync/server.rs
  38. 16 16
      shared-lib/flowy-collaboration/src/sync/synchronizer.rs
  39. 22 3
      shared-lib/flowy-core-data-model/src/entities/view/view_create.rs
  40. 169 78
      shared-lib/flowy-core-data-model/src/protobuf/model/view_create.rs
  41. 2 0
      shared-lib/flowy-core-data-model/src/protobuf/proto/view_create.proto
  42. 1 1
      shared-lib/lib-infra/src/lib.rs

+ 10 - 13
backend/src/services/core/view/controller.rs

@@ -7,10 +7,11 @@ use crate::{
     util::sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
 };
 use backend_service::errors::{invalid_params, ServerError};
+use bytes::Bytes;
 use chrono::Utc;
 use flowy_collaboration::{
-    entities::revision::{RevType, Revision},
-    protobuf::{CreateDocParams, RepeatedRevision},
+    entities::revision::{RepeatedRevision, RevType, Revision},
+    protobuf::CreateDocParams,
 };
 use flowy_core_data_model::{
     parser::{
@@ -71,12 +72,13 @@ pub(crate) async fn create_view(
     params: CreateViewParams,
     user_id: &str,
 ) -> Result<View, ServerError> {
+    let view_id = check_view_id(params.view_id.clone())?;
     let name = ViewName::parse(params.name).map_err(invalid_params)?;
     let belong_to_id = AppId::parse(params.belong_to_id).map_err(invalid_params)?;
     let thumbnail = ViewThumbnail::parse(params.thumbnail).map_err(invalid_params)?;
     let desc = ViewDesc::parse(params.desc).map_err(invalid_params)?;
 
-    let (sql, args, view) = NewViewSqlBuilder::new(belong_to_id.as_ref())
+    let (sql, args, view) = NewViewSqlBuilder::new(view_id, belong_to_id.as_ref())
         .name(name.as_ref())
         .desc(desc.as_ref())
         .thumbnail(thumbnail.as_ref())
@@ -88,18 +90,13 @@ pub(crate) async fn create_view(
         .await
         .map_err(map_sqlx_error)?;
 
-    let doc_id = view.id.clone();
-    let revision: flowy_collaboration::protobuf::Revision =
-        Revision::initial_revision(user_id, &doc_id, RevType::Remote)
-            .try_into()
-            .unwrap();
-    let mut repeated_revision = RepeatedRevision::new();
-    repeated_revision.set_items(vec![revision].into());
-
+    let delta_data = Bytes::from(params.view_data);
+    let md5 = format!("{:x}", md5::compute(&delta_data));
+    let revision = Revision::new(&view.id, 0, 0, delta_data, RevType::Remote, user_id, md5);
+    let repeated_revision = RepeatedRevision::new(vec![revision]);
     let mut create_doc_params = CreateDocParams::new();
-    create_doc_params.set_revisions(repeated_revision);
+    create_doc_params.set_revisions(repeated_revision.try_into().unwrap());
     create_doc_params.set_id(view.id.clone());
-
     let _ = create_document(&kv_store, create_doc_params).await?;
 
     Ok(view)

+ 9 - 6
backend/src/services/core/view/persistence.rs

@@ -16,12 +16,11 @@ pub struct NewViewSqlBuilder {
 }
 
 impl NewViewSqlBuilder {
-    pub fn new(belong_to_id: &str) -> Self {
-        let uuid = uuid::Uuid::new_v4();
+    pub fn new(view_id: Uuid, belong_to_id: &str) -> Self {
         let time = Utc::now();
 
         let table = ViewTable {
-            id: uuid,
+            id: view_id,
             belong_to_id: belong_to_id.to_string(),
             name: "".to_string(),
             description: "".to_string(),
@@ -94,13 +93,17 @@ impl NewViewSqlBuilder {
 pub(crate) fn check_view_ids(ids: Vec<String>) -> Result<Vec<Uuid>, ServerError> {
     let mut view_ids = vec![];
     for id in ids {
-        let view_id = ViewId::parse(id).map_err(invalid_params)?;
-        let view_id = Uuid::parse_str(view_id.as_ref())?;
-        view_ids.push(view_id);
+        view_ids.push(check_view_id(id)?);
     }
     Ok(view_ids)
 }
 
+pub(crate) fn check_view_id(id: String) -> Result<Uuid, ServerError> {
+    let view_id = ViewId::parse(id).map_err(invalid_params)?;
+    let view_id = Uuid::parse_str(view_id.as_ref())?;
+    Ok(view_id)
+}
+
 #[derive(Debug, Clone, sqlx::FromRow)]
 pub struct ViewTable {
     pub(crate) id: uuid::Uuid,

+ 8 - 2
backend/src/services/core/view/router.rs

@@ -1,7 +1,13 @@
 use crate::{
     context::FlowyPersistence,
     entities::logged_user::LoggedUser,
-    services::core::view::{create_view, delete_view, persistence::check_view_ids, read_view, update_view},
+    services::core::view::{
+        create_view,
+        delete_view,
+        persistence::{check_view_id, check_view_ids},
+        read_view,
+        update_view,
+    },
     util::serde_ext::parse_from_payload,
 };
 use actix_web::{
@@ -62,7 +68,7 @@ pub async fn read_handler(payload: Payload, pool: Data<PgPool>, user: LoggedUser
 
 pub async fn update_handler(payload: Payload, pool: Data<PgPool>) -> Result<HttpResponse, ServerError> {
     let params: UpdateViewParams = parse_from_payload(payload).await?;
-    let view_id = check_view_ids(vec![params.view_id.clone()])?.pop().unwrap();
+    let view_id = check_view_id(params.view_id.clone())?;
     let name = match params.has_name() {
         false => None,
         true => Some(ViewName::parse(params.get_name().to_owned()).map_err(invalid_params)?.0),

+ 1 - 1
backend/src/services/document/mod.rs

@@ -1,6 +1,6 @@
 #![allow(clippy::module_inception)]
 
-pub(crate) mod persistence;
+pub mod persistence;
 pub(crate) mod router;
 pub(crate) mod ws_actor;
 pub(crate) mod ws_receiver;

+ 5 - 8
backend/src/services/document/persistence.rs

@@ -1,6 +1,5 @@
 use crate::{
-    context::FlowyPersistence,
-    services::kv::{KVStore, KVTransaction, KeyValue},
+    services::kv::{KVStore, KeyValue},
     util::serde_ext::parse_from_bytes,
 };
 use anyhow::Context;
@@ -16,7 +15,7 @@ use flowy_collaboration::protobuf::{
 };
 use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
 use protobuf::Message;
-use sqlx::PgPool;
+
 use std::sync::Arc;
 use uuid::Uuid;
 
@@ -30,14 +29,12 @@ pub(crate) async fn create_document(
     Ok(())
 }
 
-#[tracing::instrument(level = "debug", skip(persistence), err)]
-pub(crate) async fn read_document(
-    persistence: &Arc<FlowyPersistence>,
+#[tracing::instrument(level = "debug", skip(kv_store), err)]
+pub async fn read_document(
+    kv_store: &Arc<DocumentKVPersistence>,
     params: DocIdentifier,
 ) -> Result<DocumentInfo, ServerError> {
     let _ = Uuid::parse_str(&params.doc_id).context("Parse document id to uuid failed")?;
-
-    let kv_store = persistence.kv_store();
     let revisions = kv_store.batch_get_revisions(&params.doc_id, None).await?;
     make_doc_from_revisions(&params.doc_id, revisions)
 }

+ 3 - 2
backend/src/services/document/router.rs

@@ -9,7 +9,7 @@ use actix_web::{
 };
 use backend_service::{errors::ServerError, response::FlowyResponse};
 use flowy_collaboration::protobuf::{CreateDocParams, DocIdentifier, ResetDocumentParams};
-use sqlx::PgPool;
+
 use std::sync::Arc;
 
 pub async fn create_document_handler(
@@ -28,7 +28,8 @@ pub async fn read_document_handler(
     persistence: Data<Arc<FlowyPersistence>>,
 ) -> Result<HttpResponse, ServerError> {
     let params: DocIdentifier = parse_from_payload(payload).await?;
-    let doc = read_document(persistence.get_ref(), params).await?;
+    let kv_store = persistence.kv_store();
+    let doc = read_document(&kv_store, params).await?;
     let response = FlowyResponse::success().pb(doc)?;
     Ok(response.into())
 }

+ 10 - 14
backend/src/services/document/ws_actor.rs

@@ -85,23 +85,19 @@ impl DocumentWebSocketActor {
             persistence,
         });
 
-        match self.handle_revision(user, document_client_data).await {
-            Ok(_) => {},
-            Err(e) => {
-                tracing::error!("[DocumentWebSocketActor]: process client data error {:?}", e);
-            },
-        }
-        Ok(())
-    }
-
-    async fn handle_revision(&self, user: Arc<ServerDocUser>, client_data: DocumentClientWSData) -> Result<()> {
-        match &client_data.ty {
+        match &document_client_data.ty {
             DocumentClientWSDataType::ClientPushRev => {
-                let _ = self
+                match self
                     .doc_manager
-                    .apply_revisions(user, client_data)
+                    .apply_revisions(user, document_client_data)
                     .await
-                    .map_err(internal_error)?;
+                    .map_err(internal_error)
+                {
+                    Ok(_) => {},
+                    Err(e) => {
+                        tracing::error!("[DocumentWebSocketActor]: process client data failed: {:?}", e);
+                    },
+                }
             },
         }
 

+ 2 - 4
backend/src/services/document/ws_receiver.rs

@@ -16,7 +16,7 @@ use flowy_collaboration::{
     errors::CollaborateError,
     protobuf::DocIdentifier,
 };
-use lib_infra::future::{BoxResultFuture, FutureResultSend};
+use lib_infra::future::BoxResultFuture;
 
 use flowy_collaboration::sync::{DocumentPersistence, ServerDocumentManager};
 use std::{
@@ -83,7 +83,7 @@ impl DocumentPersistence for DocumentPersistenceImpl {
             doc_id: doc_id.to_string(),
             ..Default::default()
         };
-        let persistence = self.0.clone();
+        let persistence = self.0.kv_store();
         Box::pin(async move {
             let mut pb_doc = read_document(&persistence, params)
                 .await
@@ -115,11 +115,9 @@ impl DocumentPersistence for DocumentPersistenceImpl {
         let kv_store = self.0.kv_store();
         let doc_id = doc_id.to_owned();
         let f = || async move {
-            let expected_len = rev_ids.len();
             let mut pb = kv_store.batch_get_revisions(&doc_id, rev_ids).await?;
             let repeated_revision: RepeatedRevision = (&mut pb).try_into()?;
             let revisions = repeated_revision.into_inner();
-            assert_eq!(expected_len, revisions.len());
             Ok(revisions)
         };
 

+ 32 - 7
backend/src/services/kv/kv.rs

@@ -1,13 +1,13 @@
 use crate::{
-    services::kv::{KVStore, KVTransaction, KeyValue},
+    services::kv::{KVTransaction, KeyValue},
     util::sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
 };
 use anyhow::Context;
 use async_trait::async_trait;
 use backend_service::errors::ServerError;
 use bytes::Bytes;
-use futures_core::future::BoxFuture;
-use lib_infra::future::{BoxResultFuture, FutureResultSend};
+
+use lib_infra::future::BoxResultFuture;
 use sql_builder::SqlBuilder as RawSqlBuilder;
 use sqlx::{
     postgres::{PgArguments, PgRow},
@@ -17,7 +17,6 @@ use sqlx::{
     Postgres,
     Row,
 };
-use std::{future::Future, pin::Pin, sync::Arc};
 
 const KV_TABLE: &str = "kv_table";
 
@@ -26,6 +25,33 @@ pub struct PostgresKV {
 }
 
 impl PostgresKV {
+    pub async fn get(&self, key: &str) -> Result<Option<Bytes>, ServerError> {
+        let key = key.to_owned();
+        self.transaction(|mut transaction| Box::pin(async move { transaction.get(&key).await }))
+            .await
+    }
+    pub async fn set(&self, key: &str, value: Bytes) -> Result<(), ServerError> {
+        let key = key.to_owned();
+        self.transaction(|mut transaction| Box::pin(async move { transaction.set(&key, value).await }))
+            .await
+    }
+
+    pub async fn remove(&self, key: &str) -> Result<(), ServerError> {
+        let key = key.to_owned();
+        self.transaction(|mut transaction| Box::pin(async move { transaction.remove(&key).await }))
+            .await
+    }
+
+    pub async fn batch_set(&self, kvs: Vec<KeyValue>) -> Result<(), ServerError> {
+        self.transaction(|mut transaction| Box::pin(async move { transaction.batch_set(kvs).await }))
+            .await
+    }
+
+    pub async fn batch_get(&self, keys: Vec<String>) -> Result<Vec<KeyValue>, ServerError> {
+        self.transaction(|mut transaction| Box::pin(async move { transaction.batch_get(keys).await }))
+            .await
+    }
+
     pub async fn transaction<F, O>(&self, f: F) -> Result<O, ServerError>
     where
         F: for<'a> FnOnce(Box<dyn KVTransaction + 'a>) -> BoxResultFuture<O, ServerError>,
@@ -61,14 +87,13 @@ impl<'a, 'b> KVTransaction for PostgresTransaction<'a, 'b> {
             .fetch_one(self.0 as &mut DBTransaction<'b>)
             .await;
 
-        let result = match result {
+        match result {
             Ok(val) => Ok(Some(Bytes::from(val.blob))),
             Err(error) => match error {
                 Error::RowNotFound => Ok(None),
                 _ => Err(map_sqlx_error(error)),
             },
-        };
-        result
+        }
     }
 
     async fn set(&mut self, key: &str, bytes: Bytes) -> Result<(), ServerError> {

+ 1 - 3
backend/src/services/kv/mod.rs

@@ -3,12 +3,10 @@ mod kv;
 
 use async_trait::async_trait;
 use bytes::Bytes;
-use futures_core::future::BoxFuture;
+
 pub(crate) use kv::*;
-use std::sync::Arc;
 
 use backend_service::errors::ServerError;
-use lib_infra::future::{BoxResultFuture, FutureResultSend};
 
 // TODO: Generic the KVStore that enable switching KVStore to another
 // implementation

+ 27 - 0
backend/tests/api_test/kv_test.rs

@@ -50,3 +50,30 @@ async fn kv_batch_set_test() {
 
     assert_eq!(kvs, kvs_from_db);
 }
+
+#[actix_rt::test]
+async fn kv_batch_get_start_with_test() {
+    let server = spawn_server().await;
+    let kv = server.app_ctx.persistence.kv_store();
+    let kvs = vec![
+        KeyValue {
+            key: "abc:1".to_string(),
+            value: "a".to_string().into(),
+        },
+        KeyValue {
+            key: "abc:2".to_string(),
+            value: "b".to_string().into(),
+        },
+    ];
+
+    kv.batch_set(kvs.clone()).await.unwrap();
+    kv.transaction(|mut transaction| {
+        Box::pin(async move {
+            let kvs_from_db = transaction.batch_get_start_with("abc").await.unwrap();
+            assert_eq!(kvs, kvs_from_db);
+            Ok(())
+        })
+    })
+    .await
+    .unwrap();
+}

+ 35 - 25
backend/tests/document_test/edit_script.rs

@@ -6,19 +6,22 @@ use flowy_document::services::doc::edit::ClientDocEditor as ClientEditDocContext
 use flowy_test::{helper::ViewTest, FlowySDKTest};
 use flowy_user::services::user::UserSession;
 use futures_util::{stream, stream::StreamExt};
-use sqlx::PgPool;
+
 use std::sync::Arc;
+use bytes::Bytes;
 use tokio::time::{sleep, Duration};
 // use crate::helper::*;
 use crate::util::helper::{spawn_server, TestServer};
 use flowy_collaboration::{entities::doc::DocIdentifier, protobuf::ResetDocumentParams};
 use lib_ot::rich_text::{RichTextAttribute, RichTextDelta};
 use parking_lot::RwLock;
-use flowy_collaboration::entities::revision::RepeatedRevision;
+use backend::services::document::persistence::{DocumentKVPersistence, read_document, reset_document};
+
+use flowy_collaboration::entities::revision::{RepeatedRevision, Revision, RevType};
 use lib_ot::core::Interval;
 
 use flowy_net::services::ws::FlowyWSConnect;
-use crate::util::helper::*;
+
 
 pub struct DocumentTest {
     server: TestServer,
@@ -32,7 +35,7 @@ pub enum DocScript {
     ClientOpenDoc,
     AssertClient(&'static str),
     AssertServer(&'static str, i64),
-    ServerSaveDocument(RepeatedRevision), // delta_json, rev_id
+    ServerSaveDocument(String, i64), // delta_json, rev_id
 }
 
 impl DocumentTest {
@@ -78,10 +81,8 @@ impl ScriptContext {
     }
 
     async fn open_doc(&mut self) {
-        let flowy_document = self.client_sdk.flowy_document.clone();
         let doc_id = self.doc_id.clone();
-
-        let edit_context = flowy_document.open(DocIdentifier { doc_id }).await.unwrap();
+        let edit_context = self.client_sdk.document_ctx.open(DocIdentifier { doc_id }).await.unwrap();
         self.client_edit_context = Some(edit_context);
     }
 
@@ -106,6 +107,7 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
                     context.write().open_doc().await;
                 },
                 DocScript::ClientInsertText(index, s) => {
+                    sleep(Duration::from_millis(2000)).await;
                     context.read().client_edit_context().insert(index, s).await.unwrap();
                 },
                 DocScript::ClientFormatText(interval, attribute) => {
@@ -123,24 +125,32 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
                 },
                 DocScript::AssertServer(s, rev_id) => {
                     sleep(Duration::from_millis(100)).await;
+                    let persistence = Data::new(context.read().server.app_ctx.persistence.kv_store());
+                    let doc_identifier: flowy_collaboration::protobuf::DocIdentifier = DocIdentifier {
+                        doc_id
+                    }.try_into().unwrap();
                     
-                    // let doc_identifier = DocIdentifier {
-                    //     doc_id
-                    // };
-                    // 
-                    // let doc = context.read().server.read_doc()
-                    
-                    
-                    // let pg_pool = context.read().server_pg_pool.clone();
-                    // let doc_manager = context.read().server_doc_manager.clone();
-                    // let edit_doc = doc_manager.get(&doc_id).await.unwrap();
-                    // let json = edit_doc.document_json().await.unwrap();
-                    // assert_eq(s, &json);
-                    // assert_eq!(edit_doc.rev_id().await.unwrap(), rev_id);
+                    let document_info = read_document(persistence.get_ref(), doc_identifier).await.unwrap();
+                    assert_eq(s, &document_info.text);
+                    assert_eq!(document_info.rev_id, rev_id);
                 },
-                DocScript::ServerSaveDocument(repeated_revision) => {
-                    let pg_pool = Data::new(context.read().server.pg_pool.clone());
-                    reset_doc(&doc_id, repeated_revision, pg_pool).await;
+                DocScript::ServerSaveDocument(document_json, rev_id) => {
+                    let delta_data = Bytes::from(document_json);
+                    let user_id = context.read().client_user_session.user_id().unwrap();
+                    let md5 = format!("{:x}", md5::compute(&delta_data));
+                    let base_rev_id = if rev_id == 0 { rev_id } else { rev_id - 1 };
+                    let revision = Revision::new(
+                        &doc_id,
+                        base_rev_id,
+                        rev_id,
+                        delta_data,
+                        RevType::Remote,
+                        &user_id,
+                        md5,
+                    );
+                    
+                    let kv_store = Data::new(context.read().server.app_ctx.persistence.kv_store());
+                    reset_doc(&doc_id, RepeatedRevision::new(vec![revision]), kv_store.get_ref()).await;
                 },
                 // DocScript::Sleep(sec) => {
                 //     sleep(Duration::from_secs(sec)).await;
@@ -174,10 +184,10 @@ async fn create_doc(flowy_test: &FlowySDKTest) -> String {
     view_test.view.id
 }
 
-async fn reset_doc(doc_id: &str, repeated_revision: RepeatedRevision, pool: Data<PgPool>) {
+async fn reset_doc(doc_id: &str, repeated_revision: RepeatedRevision, kv_store: &Arc<DocumentKVPersistence>) {
     let pb: flowy_collaboration::protobuf::RepeatedRevision = repeated_revision.try_into().unwrap();
     let mut params = ResetDocumentParams::new();
     params.set_doc_id(doc_id.to_owned());
     params.set_revisions(pb);
-    // let _ = reset_document_handler(pool.get_ref(), params).await.unwrap();
+    let _ = reset_document(kv_store, params).await.unwrap();
 }

+ 1 - 2
backend/tests/document_test/edit_test.rs

@@ -20,7 +20,6 @@ use lib_ot::{core::Interval, rich_text::RichTextAttribute};
 async fn delta_sync_while_editing() {
     let test = DocumentTest::new().await;
     test.run_scripts(vec![
-        DocScript::ClientConnectWS,
         DocScript::ClientOpenDoc,
         DocScript::ClientInsertText(0, "abc"),
         DocScript::ClientInsertText(3, "123"),
@@ -34,7 +33,6 @@ async fn delta_sync_while_editing() {
 async fn delta_sync_multi_revs() {
     let test = DocumentTest::new().await;
     test.run_scripts(vec![
-        DocScript::ClientConnectWS,
         DocScript::ClientOpenDoc,
         DocScript::ClientInsertText(0, "abc"),
         DocScript::ClientInsertText(3, "123"),
@@ -81,6 +79,7 @@ async fn delta_sync_with_http_request() {
     let mut document = Document::new::<FlowyDoc>();
     document.insert(0, "123").unwrap();
     document.insert(3, "456").unwrap();
+    
     let json = document.to_json();
 
     test.run_scripts(vec![

+ 15 - 7
backend/tests/util/helper.rs

@@ -1,5 +1,5 @@
 use backend::{
-    application::{get_connection_pool, init_app_context, Application},
+    application::{init_app_context, Application},
     config::{get_configuration, DatabaseSettings},
     context::AppContext,
 };
@@ -9,10 +9,14 @@ use backend_service::{
     user_request::*,
     workspace_request::*,
 };
-use flowy_collaboration::entities::doc::{CreateDocParams, DocIdentifier, DocumentInfo};
+use flowy_collaboration::{
+    document::default::initial_delta_string,
+    entities::doc::{CreateDocParams, DocIdentifier, DocumentInfo},
+};
 use flowy_core_data_model::entities::prelude::*;
 use flowy_document::services::server::{create_doc_request, read_doc_request};
 use flowy_user_data_model::entities::*;
+use lib_infra::uuid_string;
 use sqlx::{Connection, Executor, PgConnection, PgPool};
 use uuid::Uuid;
 
@@ -203,7 +207,6 @@ pub async fn spawn_user_server() -> TestUserServer {
 
 #[derive(Clone)]
 pub struct TestServer {
-    pub pg_pool: PgPool,
     pub app_ctx: AppContext,
     pub client_server_config: ClientServerConfiguration,
 }
@@ -234,9 +237,6 @@ pub async fn spawn_server() -> TestServer {
     client_server_config.reset_host_with_port("localhost", application_port);
 
     TestServer {
-        pg_pool: get_connection_pool(&configuration.database)
-            .await
-            .expect("Failed to connect to the database"),
         app_ctx,
         client_server_config,
     }
@@ -312,7 +312,15 @@ pub async fn create_test_view(application: &TestUserServer, app_id: &str) -> Vie
     let desc = "This is my first view".to_string();
     let thumbnail = "http://1.png".to_string();
 
-    let params = CreateViewParams::new(app_id.to_owned(), name, desc, ViewType::Doc, thumbnail);
+    let params = CreateViewParams::new(
+        app_id.to_owned(),
+        name,
+        desc,
+        ViewType::Doc,
+        thumbnail,
+        initial_delta_string(),
+        uuid_string(),
+    );
     let app = application.create_view(params).await;
     app
 }

+ 28 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-core-data-model/view_create.pb.dart

@@ -137,6 +137,8 @@ class CreateViewParams extends $pb.GeneratedMessage {
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
     ..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
+    ..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewData')
+    ..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
     ..hasRequiredFields = false
   ;
 
@@ -147,6 +149,8 @@ class CreateViewParams extends $pb.GeneratedMessage {
     $core.String? desc,
     $core.String? thumbnail,
     ViewType? viewType,
+    $core.String? viewData,
+    $core.String? viewId,
   }) {
     final _result = create();
     if (belongToId != null) {
@@ -164,6 +168,12 @@ class CreateViewParams extends $pb.GeneratedMessage {
     if (viewType != null) {
       _result.viewType = viewType;
     }
+    if (viewData != null) {
+      _result.viewData = viewData;
+    }
+    if (viewId != null) {
+      _result.viewId = viewId;
+    }
     return _result;
   }
   factory CreateViewParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -231,6 +241,24 @@ class CreateViewParams extends $pb.GeneratedMessage {
   $core.bool hasViewType() => $_has(4);
   @$pb.TagNumber(5)
   void clearViewType() => clearField(5);
+
+  @$pb.TagNumber(6)
+  $core.String get viewData => $_getSZ(5);
+  @$pb.TagNumber(6)
+  set viewData($core.String v) { $_setString(5, v); }
+  @$pb.TagNumber(6)
+  $core.bool hasViewData() => $_has(5);
+  @$pb.TagNumber(6)
+  void clearViewData() => clearField(6);
+
+  @$pb.TagNumber(7)
+  $core.String get viewId => $_getSZ(6);
+  @$pb.TagNumber(7)
+  set viewId($core.String v) { $_setString(6, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasViewId() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearViewId() => clearField(7);
 }
 
 class View extends $pb.GeneratedMessage {

+ 3 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-core-data-model/view_create.pbjson.dart

@@ -45,11 +45,13 @@ const CreateViewParams$json = const {
     const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
     const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '10': 'thumbnail'},
     const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
+    const {'1': 'view_data', '3': 6, '4': 1, '5': 9, '10': 'viewData'},
+    const {'1': 'view_id', '3': 7, '4': 1, '5': 9, '10': 'viewId'},
   ],
 };
 
 /// Descriptor for `CreateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBl');
+final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhsKCXZpZXdfZGF0YRgGIAEoCVIIdmlld0RhdGESFwoHdmlld19pZBgHIAEoCVIGdmlld0lk');
 @$core.Deprecated('Use viewDescriptor instead')
 const View$json = const {
   '1': 'View',

+ 21 - 17
frontend/rust-lib/flowy-core/src/core/core_context.rs

@@ -4,8 +4,8 @@ use chrono::Utc;
 use lazy_static::lazy_static;
 use parking_lot::RwLock;
 
-use flowy_collaboration::{document::default::initial_read_me, entities::doc::DocumentDelta};
-use flowy_core_data_model::user_default;
+use flowy_collaboration::document::default::{initial_delta, initial_read_me};
+use flowy_core_data_model::{entities::view::CreateViewParams, user_default};
 use flowy_net::entities::NetworkType;
 
 use crate::{
@@ -85,24 +85,28 @@ impl CoreContext {
         let apps = workspace.take_apps().into_inner();
         let cloned_workspace = workspace.clone();
 
-        let _ = self.workspace_controller.create_workspace(workspace).await?;
+        let _ = self.workspace_controller.create_workspace_on_local(workspace).await?;
         for mut app in apps {
+            let app_id = app.id.clone();
             let views = app.take_belongings().into_inner();
-            let _ = self.app_controller.create_app(app).await?;
+            let _ = self.app_controller.create_app_on_local(app).await?;
             for (index, view) in views.into_iter().enumerate() {
-                if index == 0 {
-                    let delta = initial_read_me();
-                    let doc_delta = DocumentDelta {
-                        doc_id: view.id.clone(),
-                        text: delta.to_json(),
-                    };
-                    let _ = self.view_controller.apply_doc_delta(doc_delta).await?;
-                    self.view_controller.set_latest_view(&view);
-
-                    // Close the view after initialize
-                    self.view_controller.close_view(view.id.clone().into()).await?;
-                }
-                let _ = self.view_controller.create_view(view).await?;
+                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?;
             }
         }
 

+ 6 - 53
frontend/rust-lib/flowy-core/src/services/app/controller.rs

@@ -40,17 +40,17 @@ impl AppController {
     }
 
     pub fn init(&self) -> Result<(), FlowyError> {
-        self.listen_trash_can_event();
+        self.listen_trash_controller_event();
         Ok(())
     }
 
     #[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name) err)]
     pub(crate) async fn create_app_from_params(&self, params: CreateAppParams) -> Result<App, FlowyError> {
         let app = self.create_app_on_server(params).await?;
-        self.create_app(app).await
+        self.create_app_on_local(app).await
     }
 
-    pub(crate) async fn create_app(&self, app: App) -> Result<App, FlowyError> {
+    pub(crate) async fn create_app_on_local(&self, app: App) -> Result<App, FlowyError> {
         let conn = &*self.database.db_connection()?;
         conn.immediate_transaction::<_, FlowyError, _>(|| {
             let _ = self.save_app(app.clone(), &*conn)?;
@@ -71,7 +71,7 @@ impl AppController {
         let conn = self.database.db_connection()?;
         let app_table = AppTableSql::read_app(&params.app_id, &*conn)?;
 
-        let trash_ids = self.trash_can.trash_ids(&conn)?;
+        let trash_ids = self.trash_can.read_trash_ids(&conn)?;
         if trash_ids.contains(&app_table.id) {
             return Err(FlowyError::record_not_found());
         }
@@ -165,7 +165,7 @@ impl AppController {
         Ok(())
     }
 
-    fn listen_trash_can_event(&self) {
+    fn listen_trash_controller_event(&self) {
         let mut rx = self.trash_can.subscribe();
         let database = self.database.clone();
         let trash_can = self.trash_can.clone();
@@ -245,56 +245,9 @@ pub fn read_local_workspace_apps(
     conn: &SqliteConnection,
 ) -> Result<RepeatedApp, FlowyError> {
     let mut app_tables = AppTableSql::read_workspace_apps(workspace_id, false, conn)?;
-    let trash_ids = trash_controller.trash_ids(conn)?;
+    let trash_ids = trash_controller.read_trash_ids(conn)?;
     app_tables.retain(|app_table| !trash_ids.contains(&app_table.id));
 
     let apps = app_tables.into_iter().map(|table| table.into()).collect::<Vec<App>>();
     Ok(RepeatedApp { items: apps })
 }
-
-// #[tracing::instrument(level = "debug", skip(self), err)]
-// pub(crate) async fn delete_app(&self, app_id: &str) -> Result<(),
-// FlowyError> {     let conn = &*self.database.db_connection()?;
-//     conn.immediate_transaction::<_, FlowyError, _>(|| {
-//         let app = AppTableSql::delete_app(app_id, &*conn)?;
-//         let apps = self.read_local_apps(&app.workspace_id, &*conn)?;
-//         send_dart_notification(&app.workspace_id,
-// WorkspaceNotification::WorkspaceDeleteApp)             .payload(apps)
-//             .send();
-//         Ok(())
-//     })?;
-//
-//     let _ = self.delete_app_on_server(app_id);
-//     Ok(())
-// }
-//
-// #[tracing::instrument(level = "debug", skip(self), err)]
-// fn delete_app_on_server(&self, app_id: &str) -> Result<(), FlowyError> {
-//     let token = self.user.token()?;
-//     let server = self.server.clone();
-//     let params = DeleteAppParams {
-//         app_id: app_id.to_string(),
-//     };
-//     spawn(async move {
-//         match server.delete_app(&token, params).await {
-//             Ok(_) => {},
-//             Err(e) => {
-//                 // TODO: retry?
-//                 log::error!("Delete app failed: {:?}", e);
-//             },
-//         }
-//     });
-//     // let action = RetryAction::new(self.server.clone(), self.user.clone(),
-// move     // |token, server| {     let params = params.clone();
-//     //     async move {
-//     //         match server.delete_app(&token, params).await {
-//     //             Ok(_) => {},
-//     //             Err(e) => log::error!("Delete app failed: {:?}", e),
-//     //         }
-//     //         Ok::<(), FlowyError>(())
-//     //     }
-//     // });
-//     //
-//     // spawn_retry(500, 3, action);
-//     Ok(())
-// }

+ 4 - 4
frontend/rust-lib/flowy-core/src/services/app/event_handler.rs

@@ -29,17 +29,17 @@ pub(crate) async fn create_app_handler(
 
 pub(crate) async fn delete_app_handler(
     data: Data<QueryAppRequest>,
-    controller: Unit<Arc<AppController>>,
-    trash_can: Unit<Arc<TrashController>>,
+    view_controller: Unit<Arc<AppController>>,
+    trash_controller: Unit<Arc<TrashController>>,
 ) -> Result<(), FlowyError> {
     let params: AppIdentifier = data.into_inner().try_into()?;
-    let trash = controller
+    let trash = view_controller
         .read_app_tables(vec![params.app_id])?
         .into_iter()
         .map(|view_table| view_table.into())
         .collect::<Vec<Trash>>();
 
-    let _ = trash_can.add(trash).await?;
+    let _ = trash_controller.add(trash).await?;
     Ok(())
 }
 

+ 4 - 4
frontend/rust-lib/flowy-core/src/services/server/server_api_mock.rs

@@ -8,7 +8,7 @@ use crate::{
     errors::FlowyError,
     services::server::WorkspaceServerAPI,
 };
-use lib_infra::{future::FutureResult, timestamp, uuid};
+use lib_infra::{future::FutureResult, timestamp, uuid_string};
 
 pub struct WorkspaceServerMock {}
 
@@ -18,7 +18,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
     fn create_workspace(&self, _token: &str, params: CreateWorkspaceParams) -> FutureResult<Workspace, FlowyError> {
         let time = timestamp();
         let workspace = Workspace {
-            id: uuid(),
+            id: uuid_string(),
             name: params.name,
             desc: params.desc,
             apps: RepeatedApp::default(),
@@ -51,7 +51,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
     fn create_view(&self, _token: &str, params: CreateViewParams) -> FutureResult<View, FlowyError> {
         let time = timestamp();
         let view = View {
-            id: uuid(),
+            id: uuid_string(),
             belong_to_id: params.belong_to_id,
             name: params.name,
             desc: params.desc,
@@ -79,7 +79,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
     fn create_app(&self, _token: &str, params: CreateAppParams) -> FutureResult<App, FlowyError> {
         let time = timestamp();
         let app = App {
-            id: uuid(),
+            id: uuid_string(),
             workspace_id: params.workspace_id,
             name: params.name,
             desc: params.desc,

+ 1 - 1
frontend/rust-lib/flowy-core/src/services/trash/controller.rs

@@ -182,7 +182,7 @@ impl TrashController {
         Ok(repeated_trash)
     }
 
-    pub fn trash_ids(&self, conn: &SqliteConnection) -> Result<Vec<String>, FlowyError> {
+    pub fn read_trash_ids(&self, conn: &SqliteConnection) -> Result<Vec<String>, FlowyError> {
         let ids = TrashTableSql::read_all(&*conn)?
             .into_inner()
             .into_iter()

+ 21 - 18
frontend/rust-lib/flowy-core/src/services/view/controller.rs

@@ -21,6 +21,7 @@ use crate::{
 use flowy_core_data_model::entities::share::{ExportData, ExportParams};
 use flowy_database::kv::KV;
 use flowy_document::context::DocumentContext;
+use lib_infra::uuid_string;
 
 const LATEST_VIEW_ID: &str = "latest_view_id";
 
@@ -57,11 +58,11 @@ impl ViewController {
 
     #[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name), err)]
     pub(crate) async fn create_view_from_params(&self, params: CreateViewParams) -> Result<View, FlowyError> {
-        let view = self.create_view_on_server(params.clone()).await?;
-        self.create_view(view).await
+        let view = self.create_view_on_server(params).await?;
+        self.create_view_on_local(view).await
     }
 
-    pub(crate) async fn create_view(&self, view: View) -> Result<View, FlowyError> {
+    pub(crate) async fn create_view_on_local(&self, view: View) -> Result<View, FlowyError> {
         let conn = &*self.database.db_connection()?;
         let trash_can = self.trash_can.clone();
 
@@ -86,7 +87,7 @@ impl ViewController {
         let conn = self.database.db_connection()?;
         let view_table = ViewTableSql::read_view(&params.view_id, &*conn)?;
 
-        let trash_ids = self.trash_can.trash_ids(&conn)?;
+        let trash_ids = self.trash_can.read_trash_ids(&conn)?;
         if trash_ids.contains(&view_table.id) {
             return Err(FlowyError::record_not_found());
         }
@@ -120,7 +121,7 @@ impl ViewController {
 
     #[tracing::instrument(level = "debug", skip(self,params), fields(doc_id = %params.doc_id), err)]
     pub(crate) async fn close_view(&self, params: DocIdentifier) -> Result<(), FlowyError> {
-        let _ = self.document_ctx.close(params).await?;
+        let _ = self.document_ctx.doc_ctrl.close(&params.doc_id)?;
         Ok(())
     }
 
@@ -131,14 +132,14 @@ impl ViewController {
                 let _ = KV::remove(LATEST_VIEW_ID);
             }
         }
-        let _ = self.document_ctx.close(params).await?;
+        let _ = self.document_ctx.doc_ctrl.close(&params.doc_id)?;
         Ok(())
     }
 
     #[tracing::instrument(level = "debug", skip(self, params), fields(doc_id = %params.doc_id), err)]
     pub(crate) async fn duplicate_view(&self, params: DocIdentifier) -> Result<(), FlowyError> {
         let view: View = ViewTableSql::read_view(&params.doc_id, &*self.database.db_connection()?)?.into();
-        let _delta_data = self
+        let delta_data = self
             .document_ctx
             .read_document_data(params, self.database.db_pool()?)
             .await?;
@@ -149,6 +150,8 @@ impl ViewController {
             desc: view.desc.clone(),
             thumbnail: "".to_owned(),
             view_type: view.view_type.clone(),
+            view_data: delta_data.text,
+            view_id: uuid_string(),
         };
 
         let _ = self.create_view_from_params(duplicate_params).await?;
@@ -174,7 +177,7 @@ impl ViewController {
     pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, FlowyError> {
         // TODO: read from server
         let conn = self.database.db_connection()?;
-        let repeated_view = read_local_belonging_view(belong_to_id, self.trash_can.clone(), &conn)?;
+        let repeated_view = read_belonging_views_on_local(belong_to_id, self.trash_can.clone(), &conn)?;
         Ok(repeated_view)
     }
 
@@ -195,13 +198,13 @@ impl ViewController {
 
         //
         let _ = notify_views_changed(&updated_view.belong_to_id, self.trash_can.clone(), conn)?;
-
         let _ = self.update_view_on_server(params);
         Ok(updated_view)
     }
 
     pub(crate) async fn apply_doc_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> {
-        let doc = self.document_ctx.apply_doc_delta(params).await?;
+        let db_pool = self.document_ctx.user.db_pool()?;
+        let doc = self.document_ctx.doc_ctrl.apply_local_delta(params, db_pool).await?;
         Ok(doc)
     }
 
@@ -308,7 +311,7 @@ async fn handle_trash_event(
         TrashEvent::NewTrash(identifiers, ret) => {
             let result = || {
                 let conn = &*db_result?;
-                let view_tables = get_view_table_from(identifiers, conn)?;
+                let view_tables = read_view_tables(identifiers, conn)?;
                 for view_table in view_tables {
                     let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?;
                     notify_dart(view_table, WorkspaceNotification::ViewDeleted);
@@ -320,7 +323,7 @@ async fn handle_trash_event(
         TrashEvent::Putback(identifiers, ret) => {
             let result = || {
                 let conn = &*db_result?;
-                let view_tables = get_view_table_from(identifiers, conn)?;
+                let view_tables = read_view_tables(identifiers, conn)?;
                 for view_table in view_tables {
                     let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?;
                     notify_dart(view_table, WorkspaceNotification::ViewRestored);
@@ -337,7 +340,7 @@ async fn handle_trash_event(
                     for identifier in identifiers.items {
                         let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
                         let _ = ViewTableSql::delete_view(&identifier.id, conn)?;
-                        let _ = context.delete(identifier.id.clone().into())?;
+                        let _ = context.doc_ctrl.delete(identifier.id.clone().into())?;
                         notify_ids.insert(view_table.belong_to_id);
                     }
 
@@ -354,7 +357,7 @@ async fn handle_trash_event(
     }
 }
 
-fn get_view_table_from(identifiers: TrashIdentifiers, conn: &SqliteConnection) -> Result<Vec<ViewTable>, FlowyError> {
+fn read_view_tables(identifiers: TrashIdentifiers, conn: &SqliteConnection) -> Result<Vec<ViewTable>, FlowyError> {
     let mut view_tables = vec![];
     let _ = conn.immediate_transaction::<_, FlowyError, _>(|| {
         for identifier in identifiers.items {
@@ -377,7 +380,7 @@ fn notify_views_changed(
     trash_can: Arc<TrashController>,
     conn: &SqliteConnection,
 ) -> FlowyResult<()> {
-    let repeated_view = read_local_belonging_view(belong_to_id, trash_can.clone(), conn)?;
+    let repeated_view = read_belonging_views_on_local(belong_to_id, trash_can.clone(), conn)?;
     tracing::Span::current().record("view_count", &format!("{}", repeated_view.len()).as_str());
     send_dart_notification(&belong_to_id, WorkspaceNotification::AppViewsChanged)
         .payload(repeated_view)
@@ -385,13 +388,13 @@ fn notify_views_changed(
     Ok(())
 }
 
-fn read_local_belonging_view(
+fn read_belonging_views_on_local(
     belong_to_id: &str,
-    trash_can: Arc<TrashController>,
+    trash_controller: Arc<TrashController>,
     conn: &SqliteConnection,
 ) -> FlowyResult<RepeatedView> {
     let mut view_tables = ViewTableSql::read_views(belong_to_id, conn)?;
-    let trash_ids = trash_can.trash_ids(conn)?;
+    let trash_ids = trash_controller.read_trash_ids(conn)?;
     view_tables.retain(|view_table| !trash_ids.contains(&view_table.id));
 
     let views = view_tables

+ 5 - 5
frontend/rust-lib/flowy-core/src/services/view/event_handler.rs

@@ -62,21 +62,21 @@ pub(crate) async fn apply_doc_delta_handler(
 
 pub(crate) async fn delete_view_handler(
     data: Data<QueryViewRequest>,
-    controller: Unit<Arc<ViewController>>,
-    trash_can: Unit<Arc<TrashController>>,
+    view_controller: Unit<Arc<ViewController>>,
+    trash_controller: Unit<Arc<TrashController>>,
 ) -> Result<(), FlowyError> {
     let params: ViewIdentifiers = data.into_inner().try_into()?;
     for view_id in &params.view_ids {
-        let _ = controller.delete_view(view_id.into()).await;
+        let _ = view_controller.delete_view(view_id.into()).await;
     }
 
-    let trash = controller
+    let trash = view_controller
         .read_view_tables(params.view_ids)?
         .into_iter()
         .map(|view_table| view_table.into())
         .collect::<Vec<Trash>>();
 
-    let _ = trash_can.add(trash).await?;
+    let _ = trash_controller.add(trash).await?;
     Ok(())
 }
 

+ 2 - 2
frontend/rust-lib/flowy-core/src/services/workspace/controller.rs

@@ -42,10 +42,10 @@ impl WorkspaceController {
         params: CreateWorkspaceParams,
     ) -> Result<Workspace, FlowyError> {
         let workspace = self.create_workspace_on_server(params.clone()).await?;
-        self.create_workspace(workspace).await
+        self.create_workspace_on_local(workspace).await
     }
 
-    pub(crate) async fn create_workspace(&self, workspace: Workspace) -> Result<Workspace, FlowyError> {
+    pub(crate) async fn create_workspace_on_local(&self, workspace: Workspace) -> Result<Workspace, FlowyError> {
         let user_id = self.user.user_id()?;
         let token = self.user.token()?;
         let workspace_table = WorkspaceTable::new(workspace.clone(), &user_id);

+ 2 - 22
frontend/rust-lib/flowy-document/src/context.rs

@@ -19,8 +19,8 @@ pub trait DocumentUser: Send + Sync {
 }
 
 pub struct DocumentContext {
-    doc_ctrl: Arc<DocController>,
-    user: Arc<dyn DocumentUser>,
+    pub doc_ctrl: Arc<DocController>,
+    pub user: Arc<dyn DocumentUser>,
 }
 
 impl DocumentContext {
@@ -40,21 +40,11 @@ impl DocumentContext {
         Ok(())
     }
 
-    pub fn delete(&self, params: DocIdentifier) -> Result<(), FlowyError> {
-        let _ = self.doc_ctrl.delete(params)?;
-        Ok(())
-    }
-
     pub async fn open(&self, params: DocIdentifier) -> Result<Arc<ClientDocEditor>, FlowyError> {
         let edit_context = self.doc_ctrl.open(params, self.user.db_pool()?).await?;
         Ok(edit_context)
     }
 
-    pub async fn close(&self, params: DocIdentifier) -> Result<(), FlowyError> {
-        let _ = self.doc_ctrl.close(&params.doc_id)?;
-        Ok(())
-    }
-
     pub async fn read_document_data(
         &self,
         params: DocIdentifier,
@@ -64,14 +54,4 @@ impl DocumentContext {
         let delta = edit_context.delta().await?;
         Ok(delta)
     }
-
-    pub async fn apply_doc_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> {
-        // workaround: compare the rust's delta with flutter's delta. Will be removed
-        // very soon
-        let doc = self
-            .doc_ctrl
-            .apply_local_delta(params.clone(), self.user.db_pool()?)
-            .await?;
-        Ok(doc)
-    }
 }

+ 5 - 5
frontend/rust-lib/flowy-document/src/services/controller.rs

@@ -20,7 +20,7 @@ use flowy_error::FlowyResult;
 use lib_infra::future::FutureResult;
 use std::sync::Arc;
 
-pub(crate) struct DocController {
+pub struct DocController {
     server: Server,
     ws_receivers: Arc<DocumentWSReceivers>,
     ws_sender: Arc<dyn DocumentWebSocket>,
@@ -52,7 +52,7 @@ impl DocController {
         Ok(())
     }
 
-    pub(crate) async fn open(
+    pub async fn open(
         &self,
         params: DocIdentifier,
         pool: Arc<ConnectionPool>,
@@ -66,7 +66,7 @@ impl DocController {
         Ok(edit_doc_ctx)
     }
 
-    pub(crate) fn close(&self, doc_id: &str) -> Result<(), FlowyError> {
+    pub fn close(&self, doc_id: &str) -> Result<(), FlowyError> {
         tracing::debug!("Close document {}", doc_id);
         self.open_cache.remove(doc_id);
         self.ws_receivers.remove_receiver(doc_id);
@@ -74,7 +74,7 @@ impl DocController {
     }
 
     #[tracing::instrument(level = "debug", skip(self), err)]
-    pub(crate) fn delete(&self, params: DocIdentifier) -> Result<(), FlowyError> {
+    pub fn delete(&self, params: DocIdentifier) -> Result<(), FlowyError> {
         let doc_id = &params.doc_id;
         self.open_cache.remove(doc_id);
         self.ws_receivers.remove_receiver(doc_id);
@@ -86,7 +86,7 @@ impl DocController {
     // json : {"retain":7,"attributes":{"bold":null}}
     // deserialize delta: [ {retain: 7, attributes: {Bold: AttributeValue(None)}} ]
     #[tracing::instrument(level = "debug", skip(self, delta, db_pool), fields(doc_id = %delta.doc_id), err)]
-    pub(crate) async fn apply_local_delta(
+    pub async fn apply_local_delta(
         &self,
         delta: DocumentDelta,
         db_pool: Arc<ConnectionPool>,

+ 2 - 2
frontend/rust-lib/flowy-sdk/src/lib.rs

@@ -70,7 +70,7 @@ pub struct FlowySDK {
     #[allow(dead_code)]
     config: FlowySDKConfig,
     pub user_session: Arc<UserSession>,
-    pub flowy_document: Arc<DocumentContext>,
+    pub document_ctx: Arc<DocumentContext>,
     pub core: Arc<CoreContext>,
     pub dispatcher: Arc<EventDispatcher>,
     pub ws_manager: Arc<FlowyWSConnect>,
@@ -101,7 +101,7 @@ impl FlowySDK {
         Self {
             config,
             user_session,
-            flowy_document,
+            document_ctx: flowy_document,
             core: core_ctx,
             dispatcher,
             ws_manager,

+ 1 - 1
frontend/rust-lib/flowy-test/src/doc_script.rs

@@ -31,7 +31,7 @@ impl EditorTest {
         let _ = sdk.init_user().await;
         let test = ViewTest::new(&sdk).await;
         let doc_identifier: DocIdentifier = test.view.id.clone().into();
-        let editor = sdk.flowy_document.open(doc_identifier).await.unwrap();
+        let editor = sdk.document_ctx.open(doc_identifier).await.unwrap();
         Self { sdk, editor }
     }
 

+ 2 - 2
frontend/rust-lib/flowy-test/src/helper.rs

@@ -17,7 +17,7 @@ use flowy_user::{
     event::UserEvent::{InitUser, SignIn, SignOut, SignUp},
 };
 use lib_dispatch::prelude::{EventDispatcher, ModuleRequest, ToBytes};
-use lib_infra::uuid;
+use lib_infra::uuid_string;
 
 use crate::prelude::*;
 
@@ -293,7 +293,7 @@ pub fn root_dir() -> String {
     root_dir
 }
 
-pub fn random_email() -> String { format!("{}@appflowy.io", uuid()) }
+pub fn random_email() -> String { format!("{}@appflowy.io", uuid_string()) }
 
 pub fn login_email() -> String { "[email protected]".to_string() }
 

+ 2 - 2
frontend/rust-lib/flowy-test/src/lib.rs

@@ -6,7 +6,7 @@ use crate::helper::*;
 use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration};
 use flowy_sdk::{FlowySDK, FlowySDKConfig};
 use flowy_user::entities::UserProfile;
-use lib_infra::uuid;
+use lib_infra::uuid_string;
 
 pub mod prelude {
     pub use crate::{event_builder::*, helper::*, *};
@@ -31,7 +31,7 @@ impl FlowySDKTest {
     }
 
     pub fn setup_with(server_config: ClientServerConfiguration) -> Self {
-        let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid()).log_filter("debug");
+        let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid_string()).log_filter("debug");
         let sdk = FlowySDK::new(config);
         Self(sdk)
     }

+ 3 - 3
frontend/rust-lib/flowy-user/src/services/server/server_api_mock.rs

@@ -4,7 +4,7 @@ use crate::{
 };
 
 use crate::services::server::UserServerAPI;
-use lib_infra::{future::FutureResult, uuid};
+use lib_infra::{future::FutureResult, uuid_string};
 
 pub struct UserServerMock {}
 
@@ -12,7 +12,7 @@ impl UserServerMock {}
 
 impl UserServerAPI for UserServerMock {
     fn sign_up(&self, params: SignUpParams) -> FutureResult<SignUpResponse, FlowyError> {
-        let uid = uuid();
+        let uid = uuid_string();
         FutureResult::new(async move {
             Ok(SignUpResponse {
                 user_id: uid.clone(),
@@ -24,7 +24,7 @@ impl UserServerAPI for UserServerMock {
     }
 
     fn sign_in(&self, params: SignInParams) -> FutureResult<SignInResponse, FlowyError> {
-        let user_id = uuid();
+        let user_id = uuid_string();
         FutureResult::new(async {
             Ok(SignInResponse {
                 user_id: user_id.clone(),

+ 2 - 2
frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs

@@ -1,7 +1,7 @@
 use crate::helper::*;
 use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
 use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
-use lib_infra::uuid;
+use lib_infra::uuid_string;
 use serial_test::*;
 
 #[tokio::test]
@@ -53,7 +53,7 @@ async fn user_update_with_name() {
 async fn user_update_with_email() {
     let sdk = FlowySDKTest::setup();
     let user = sdk.init_user().await;
-    let new_email = format!("{}@gmail.com", uuid());
+    let new_email = format!("{}@gmail.com", uuid_string());
     let request = UpdateUserRequest::new(&user.id).email(&new_email);
     let _ = UserModuleEventBuilder::new(sdk.clone())
         .event(UpdateUser)

+ 1 - 1
frontend/rust-lib/flowy-virtual-net/src/mock/server.rs

@@ -2,7 +2,7 @@ use bytes::Bytes;
 use dashmap::DashMap;
 use flowy_collaboration::{entities::prelude::*, errors::CollaborateError, sync::*};
 // use flowy_net::services::ws::*;
-use lib_infra::future::{BoxResultFuture, FutureResultSend};
+use lib_infra::future::BoxResultFuture;
 use lib_ws::{WSModule, WebSocketRawMessage};
 use std::{
     convert::TryInto,

+ 1 - 1
shared-lib/backend-service/src/configuration.rs

@@ -54,7 +54,7 @@ impl ClientServerConfiguration {
 
     pub fn view_url(&self) -> String { format!("{}/api/view", self.base_url()) }
 
-    pub fn doc_url(&self) -> String { format!("{}/api/document", self.base_url()) }
+    pub fn doc_url(&self) -> String { format!("{}/api/doc", self.base_url()) }
 
     pub fn trash_url(&self) -> String { format!("{}/api/trash", self.base_url()) }
 

+ 6 - 3
shared-lib/flowy-collaboration/src/entities/doc.rs

@@ -1,6 +1,6 @@
 use crate::{
     entities::revision::{RepeatedRevision, Revision},
-    errors::{internal_error, CollaborateError},
+    errors::CollaborateError,
 };
 use flowy_derive::ProtoBuf;
 use lib_ot::{core::OperationTransformable, errors::OTError, rich_text::RichTextDelta};
@@ -43,8 +43,11 @@ impl DocumentInfo {
         for revision in revisions {
             base_rev_id = revision.base_rev_id;
             rev_id = revision.rev_id;
-            let delta = RichTextDelta::from_bytes(revision.delta_data).map_err(internal_error)?;
-            document_delta = document_delta.compose(&delta).map_err(internal_error)?;
+            let delta = RichTextDelta::from_bytes(revision.delta_data)
+                .map_err(|e| CollaborateError::internal().context(format!("Parser revision failed. {:?}", e)))?;
+            document_delta = document_delta
+                .compose(&delta)
+                .map_err(|e| CollaborateError::internal().context(format!("Compose delta failed. {:?}", e)))?;
         }
         let text = document_delta.to_json();
 

+ 0 - 7
shared-lib/flowy-collaboration/src/entities/revision.rs

@@ -1,4 +1,3 @@
-use crate::document::default::initial_delta;
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use lib_ot::rich_text::RichTextDelta;
@@ -43,12 +42,6 @@ impl Revision {
     #[allow(dead_code)]
     pub fn is_initial(&self) -> bool { self.rev_id == 0 }
 
-    pub fn initial_revision(user_id: &str, doc_id: &str, ty: RevType) -> Self {
-        let delta_data = initial_delta().to_bytes();
-        let md5 = format!("{:x}", md5::compute(&delta_data));
-        Revision::new(doc_id, 0, 0, delta_data, ty, user_id, md5)
-    }
-
     pub fn new(
         doc_id: &str,
         base_rev_id: i64,

+ 37 - 15
shared-lib/flowy-collaboration/src/sync/server.rs

@@ -12,7 +12,7 @@ use crate::{
 use async_stream::stream;
 use dashmap::DashMap;
 use futures::stream::StreamExt;
-use lib_infra::future::{BoxResultFuture, FutureResultSend};
+use lib_infra::future::BoxResultFuture;
 use lib_ot::rich_text::RichTextDelta;
 use std::{convert::TryFrom, fmt::Debug, sync::Arc};
 use tokio::{
@@ -62,14 +62,13 @@ impl ServerDocumentManager {
 
         let result = match self.get_document_handler(&doc_id).await {
             None => {
-                let _ = self.create_document(&doc_id, revisions).await.map_err(internal_error)?;
+                let _ = self.create_document(&doc_id, revisions).await.map_err(|e| {
+                    CollaborateError::internal().context(format!("Server crate document failed: {}", e))
+                })?;
                 Ok(())
             },
             Some(handler) => {
-                let _ = handler
-                    .apply_revisions(doc_id.clone(), user, revisions)
-                    .await
-                    .map_err(internal_error)?;
+                let _ = handler.apply_revisions(doc_id.clone(), user, revisions).await?;
                 Ok(())
             },
         };
@@ -102,6 +101,7 @@ impl ServerDocumentManager {
         }
     }
 
+    #[tracing::instrument(level = "debug", skip(self, revisions), err)]
     async fn create_document(
         &self,
         doc_id: &str,
@@ -117,7 +117,7 @@ impl ServerDocumentManager {
         let persistence = self.persistence.clone();
         let handle = spawn_blocking(|| OpenDocHandle::new(doc, persistence))
             .await
-            .map_err(internal_error)?;
+            .map_err(|e| CollaborateError::internal().context(format!("Create open doc handler failed: {}", e)))?;
         let handle = Arc::new(handle?);
         self.open_doc_map.insert(doc_id, handle.clone());
         Ok(handle)
@@ -125,6 +125,7 @@ impl ServerDocumentManager {
 }
 
 struct OpenDocHandle {
+    doc_id: String,
     sender: mpsc::Sender<DocumentCommand>,
     persistence: Arc<dyn DocumentPersistence>,
     users: DashMap<String, Arc<dyn RevisionUser>>,
@@ -132,17 +133,20 @@ struct OpenDocHandle {
 
 impl OpenDocHandle {
     fn new(doc: DocumentInfo, persistence: Arc<dyn DocumentPersistence>) -> Result<Self, CollaborateError> {
+        let doc_id = doc.doc_id.clone();
         let (sender, receiver) = mpsc::channel(100);
         let users = DashMap::new();
         let queue = DocumentCommandQueue::new(receiver, doc)?;
         tokio::task::spawn(queue.run());
         Ok(Self {
+            doc_id,
             sender,
             persistence,
             users,
         })
     }
 
+    #[tracing::instrument(level = "debug", skip(self, user, revisions), err)]
     async fn apply_revisions(
         &self,
         doc_id: String,
@@ -159,18 +163,28 @@ impl OpenDocHandle {
             persistence,
             ret,
         };
+
         let _ = self.send(msg, rx).await?;
         Ok(())
     }
 
     async fn send<T>(&self, msg: DocumentCommand, rx: oneshot::Receiver<T>) -> CollaborateResult<T> {
-        let _ = self.sender.send(msg).await.map_err(internal_error)?;
-        let result = rx.await.map_err(internal_error)?;
-        Ok(result)
+        let _ = self
+            .sender
+            .send(msg)
+            .await
+            .map_err(|e| CollaborateError::internal().context(format!("Send document command failed: {}", e)))?;
+        Ok(rx.await.map_err(internal_error)?)
     }
 }
 
-#[derive(Debug)]
+impl std::ops::Drop for OpenDocHandle {
+    fn drop(&mut self) {
+        log::debug!("{} OpenDocHandle drop", self.doc_id);
+    }
+}
+
+// #[derive(Debug)]
 enum DocumentCommand {
     ApplyRevisions {
         doc_id: String,
@@ -229,12 +243,20 @@ impl DocumentCommandQueue {
                 persistence,
                 ret,
             } => {
-                self.synchronizer
-                    .apply_revisions(doc_id, user, revisions, persistence)
+                let result = self
+                    .synchronizer
+                    .sync_revisions(doc_id, user, revisions, persistence)
                     .await
-                    .unwrap();
-                let _ = ret.send(Ok(()));
+                    .map_err(internal_error);
+                log::debug!("handle message {:?}", result);
+                let _ = ret.send(result);
             },
         }
     }
 }
+
+impl std::ops::Drop for DocumentCommandQueue {
+    fn drop(&mut self) {
+        log::debug!("{} DocumentCommandQueue drop", self.doc_id);
+    }
+}

+ 16 - 16
shared-lib/flowy-collaboration/src/sync/synchronizer.rs

@@ -4,10 +4,11 @@ use crate::{
         revision::{Revision, RevisionRange},
         ws::{DocumentServerWSData, DocumentServerWSDataBuilder},
     },
+    errors::CollaborateError,
     sync::DocumentPersistence,
 };
-use futures::TryFutureExt;
-use lib_ot::{core::OperationTransformable, errors::OTError, rich_text::RichTextDelta};
+
+use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
 use parking_lot::RwLock;
 use std::{
     cmp::Ordering,
@@ -48,19 +49,16 @@ impl RevisionSynchronizer {
     }
 
     #[tracing::instrument(level = "debug", skip(self, user, revisions, persistence), err)]
-    pub async fn apply_revisions(
+    pub async fn sync_revisions(
         &self,
         doc_id: String,
         user: Arc<dyn RevisionUser>,
         revisions: Vec<Revision>,
         persistence: Arc<dyn DocumentPersistence>,
-    ) -> Result<(), OTError> {
+    ) -> Result<(), CollaborateError> {
         if revisions.is_empty() {
             // Return all the revisions to client
-            let revisions = persistence
-                .get_doc_revisions(&doc_id)
-                .map_err(|e| OTError::internal().context(e))
-                .await?;
+            let revisions = persistence.get_doc_revisions(&doc_id).await?;
             let data = DocumentServerWSDataBuilder::build_push_message(&doc_id, revisions);
             user.receive(SyncResponse::Push(data));
             return Ok(());
@@ -78,10 +76,8 @@ impl RevisionSynchronizer {
                 let server_rev_id = next(server_base_rev_id);
                 if server_base_rev_id == first_revision.base_rev_id || server_rev_id == first_revision.rev_id {
                     // The rev is in the right order, just compose it.
-                    {
-                        for revision in &revisions {
-                            let _ = self.compose_revision(revision)?;
-                        }
+                    for revision in &revisions {
+                        let _ = self.compose_revision(revision)?;
                     }
                     user.receive(SyncResponse::NewRevision(revisions));
                 } else {
@@ -108,7 +104,11 @@ impl RevisionSynchronizer {
                 let rev_ids: Vec<i64> = (from_rev_id..=to_rev_id).collect();
                 let revisions = match persistence.get_revisions(&self.doc_id, rev_ids).await {
                     Ok(revisions) => {
-                        assert_eq!(revisions.is_empty(), false);
+                        assert_eq!(
+                            revisions.is_empty(),
+                            false,
+                            "revisions should not be empty if the doc exists"
+                        );
                         revisions
                     },
                     Err(e) => {
@@ -126,7 +126,7 @@ impl RevisionSynchronizer {
 
     pub fn doc_json(&self) -> String { self.document.read().to_json() }
 
-    fn compose_revision(&self, revision: &Revision) -> Result<(), OTError> {
+    fn compose_revision(&self, revision: &Revision) -> Result<(), CollaborateError> {
         let delta = RichTextDelta::from_bytes(&revision.delta_data)?;
         let _ = self.compose_delta(delta)?;
         let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id));
@@ -134,13 +134,13 @@ impl RevisionSynchronizer {
     }
 
     #[tracing::instrument(level = "debug", skip(self, revision))]
-    fn transform_revision(&self, revision: &Revision) -> Result<(RichTextDelta, RichTextDelta), OTError> {
+    fn transform_revision(&self, revision: &Revision) -> Result<(RichTextDelta, RichTextDelta), CollaborateError> {
         let cli_delta = RichTextDelta::from_bytes(&revision.delta_data)?;
         let result = self.document.read().delta().transform(&cli_delta)?;
         Ok(result)
     }
 
-    fn compose_delta(&self, delta: RichTextDelta) -> Result<(), OTError> {
+    fn compose_delta(&self, delta: RichTextDelta) -> Result<(), CollaborateError> {
         if delta.is_empty() {
             log::warn!("Composed delta is empty");
         }

+ 22 - 3
shared-lib/flowy-core-data-model/src/entities/view/view_create.rs

@@ -7,7 +7,7 @@ use crate::{
         view::{ViewName, ViewThumbnail},
     },
 };
-
+use flowy_collaboration::document::default::initial_delta_string;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use std::convert::TryInto;
 
@@ -68,16 +68,32 @@ pub struct CreateViewParams {
 
     #[pb(index = 5)]
     pub view_type: ViewType,
+
+    #[pb(index = 6)]
+    pub view_data: String,
+
+    #[pb(index = 7)]
+    pub view_id: String,
 }
 
 impl CreateViewParams {
-    pub fn new(belong_to_id: String, name: String, desc: String, view_type: ViewType, thumbnail: String) -> Self {
+    pub fn new(
+        belong_to_id: String,
+        name: String,
+        desc: String,
+        view_type: ViewType,
+        thumbnail: String,
+        view_data: String,
+        view_id: String,
+    ) -> Self {
         Self {
             belong_to_id,
             name,
             desc,
             thumbnail,
             view_type,
+            view_data,
+            view_id,
         }
     }
 }
@@ -88,7 +104,8 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
     fn try_into(self) -> Result<CreateViewParams, Self::Error> {
         let name = ViewName::parse(self.name)?.0;
         let belong_to_id = AppId::parse(self.belong_to_id)?.0;
-
+        let view_data = initial_delta_string();
+        let view_id = uuid::Uuid::new_v4().to_string();
         let thumbnail = match self.thumbnail {
             None => "".to_string(),
             Some(thumbnail) => ViewThumbnail::parse(thumbnail)?.0,
@@ -100,6 +117,8 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
             self.desc,
             self.view_type,
             thumbnail,
+            view_data,
+            view_id,
         ))
     }
 }

+ 169 - 78
shared-lib/flowy-core-data-model/src/protobuf/model/view_create.rs

@@ -387,6 +387,8 @@ pub struct CreateViewParams {
     pub desc: ::std::string::String,
     pub thumbnail: ::std::string::String,
     pub view_type: ViewType,
+    pub view_data: ::std::string::String,
+    pub view_id: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -521,6 +523,58 @@ impl CreateViewParams {
     pub fn set_view_type(&mut self, v: ViewType) {
         self.view_type = v;
     }
+
+    // string view_data = 6;
+
+
+    pub fn get_view_data(&self) -> &str {
+        &self.view_data
+    }
+    pub fn clear_view_data(&mut self) {
+        self.view_data.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_view_data(&mut self, v: ::std::string::String) {
+        self.view_data = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_view_data(&mut self) -> &mut ::std::string::String {
+        &mut self.view_data
+    }
+
+    // Take field
+    pub fn take_view_data(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.view_data, ::std::string::String::new())
+    }
+
+    // string view_id = 7;
+
+
+    pub fn get_view_id(&self) -> &str {
+        &self.view_id
+    }
+    pub fn clear_view_id(&mut self) {
+        self.view_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_view_id(&mut self, v: ::std::string::String) {
+        self.view_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_view_id(&mut self) -> &mut ::std::string::String {
+        &mut self.view_id
+    }
+
+    // Take field
+    pub fn take_view_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.view_id, ::std::string::String::new())
+    }
 }
 
 impl ::protobuf::Message for CreateViewParams {
@@ -547,6 +601,12 @@ impl ::protobuf::Message for CreateViewParams {
                 5 => {
                     ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.view_type, 5, &mut self.unknown_fields)?
                 },
+                6 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.view_data)?;
+                },
+                7 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.view_id)?;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -574,6 +634,12 @@ impl ::protobuf::Message for CreateViewParams {
         if self.view_type != ViewType::Blank {
             my_size += ::protobuf::rt::enum_size(5, self.view_type);
         }
+        if !self.view_data.is_empty() {
+            my_size += ::protobuf::rt::string_size(6, &self.view_data);
+        }
+        if !self.view_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(7, &self.view_id);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -595,6 +661,12 @@ impl ::protobuf::Message for CreateViewParams {
         if self.view_type != ViewType::Blank {
             os.write_enum(5, ::protobuf::ProtobufEnum::value(&self.view_type))?;
         }
+        if !self.view_data.is_empty() {
+            os.write_string(6, &self.view_data)?;
+        }
+        if !self.view_id.is_empty() {
+            os.write_string(7, &self.view_id)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -658,6 +730,16 @@ impl ::protobuf::Message for CreateViewParams {
                 |m: &CreateViewParams| { &m.view_type },
                 |m: &mut CreateViewParams| { &mut m.view_type },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "view_data",
+                |m: &CreateViewParams| { &m.view_data },
+                |m: &mut CreateViewParams| { &mut m.view_data },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "view_id",
+                |m: &CreateViewParams| { &m.view_id },
+                |m: &mut CreateViewParams| { &mut m.view_id },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateViewParams>(
                 "CreateViewParams",
                 fields,
@@ -679,6 +761,8 @@ impl ::protobuf::Clear for CreateViewParams {
         self.desc.clear();
         self.thumbnail.clear();
         self.view_type = ViewType::Blank;
+        self.view_data.clear();
+        self.view_id.clear();
         self.unknown_fields.clear();
     }
 }
@@ -1394,88 +1478,95 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     long_to_id\x18\x01\x20\x01(\tR\nbelongToId\x12\x12\n\x04name\x18\x02\x20\
     \x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12\x1e\n\
     \tthumbnail\x18\x04\x20\x01(\tH\0R\tthumbnail\x12&\n\tview_type\x18\x05\
-    \x20\x01(\x0e2\t.ViewTypeR\x08viewTypeB\x12\n\x10one_of_thumbnail\"\xa2\
+    \x20\x01(\x0e2\t.ViewTypeR\x08viewTypeB\x12\n\x10one_of_thumbnail\"\xd8\
     \x01\n\x10CreateViewParams\x12\x20\n\x0cbelong_to_id\x18\x01\x20\x01(\tR\
     \nbelongToId\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04\
     desc\x18\x03\x20\x01(\tR\x04desc\x12\x1c\n\tthumbnail\x18\x04\x20\x01(\t\
     R\tthumbnail\x12&\n\tview_type\x18\x05\x20\x01(\x0e2\t.ViewTypeR\x08view\
-    Type\"\x97\x02\n\x04View\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\
-    \x20\n\x0cbelong_to_id\x18\x02\x20\x01(\tR\nbelongToId\x12\x12\n\x04name\
-    \x18\x03\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x04\x20\x01(\tR\x04de\
-    sc\x12&\n\tview_type\x18\x05\x20\x01(\x0e2\t.ViewTypeR\x08viewType\x12\
-    \x18\n\x07version\x18\x06\x20\x01(\x03R\x07version\x12-\n\nbelongings\
-    \x18\x07\x20\x01(\x0b2\r.RepeatedViewR\nbelongings\x12#\n\rmodified_time\
-    \x18\x08\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcreate_time\x18\t\
-    \x20\x01(\x03R\ncreateTime\"+\n\x0cRepeatedView\x12\x1b\n\x05items\x18\
-    \x01\x20\x03(\x0b2\x05.ViewR\x05items*\x1e\n\x08ViewType\x12\t\n\x05Blan\
-    k\x10\0\x12\x07\n\x03Doc\x10\x01J\xd1\n\n\x06\x12\x04\0\0!\x01\n\x08\n\
-    \x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x08\x01\n\n\n\x03\
-    \x04\0\x01\x12\x03\x02\x08\x19\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\
-    \x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\
-    \0\x01\x12\x03\x03\x0b\x17\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x1a\
-    \x1b\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\
-    \x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\
-    \x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\
-    \x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x05\
-    \x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\
-    \0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\n\x04\x04\0\x08\0\x12\x03\x06\
-    \x044\n\x0c\n\x05\x04\0\x08\0\x01\x12\x03\x06\n\x1a\n\x0b\n\x04\x04\0\
-    \x02\x03\x12\x03\x06\x1d2\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\x06\x1d#\
-    \n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06$-\n\x0c\n\x05\x04\0\x02\x03\
-    \x03\x12\x03\x0601\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x07\x04\x1b\n\x0c\n\
-    \x05\x04\0\x02\x04\x06\x12\x03\x07\x04\x0c\n\x0c\n\x05\x04\0\x02\x04\x01\
-    \x12\x03\x07\r\x16\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x07\x19\x1a\n\n\
-    \n\x02\x04\x01\x12\x04\t\0\x0f\x01\n\n\n\x03\x04\x01\x01\x12\x03\t\x08\
-    \x18\n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\x1c\n\x0c\n\x05\x04\x01\x02\
-    \0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\n\x0b\x17\n\
-    \x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x1a\x1b\n\x0b\n\x04\x04\x01\x02\
-    \x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x0b\x04\
-    \n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x0b\x0b\x0f\n\x0c\n\x05\x04\
-    \x01\x02\x01\x03\x12\x03\x0b\x12\x13\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\
-    \x0c\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\x0c\x04\n\n\x0c\n\
-    \x05\x04\x01\x02\x02\x01\x12\x03\x0c\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x02\
-    \x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x01\x02\x03\x12\x03\r\x04\x19\n\
-    \x0c\n\x05\x04\x01\x02\x03\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x01\x02\
-    \x03\x01\x12\x03\r\x0b\x14\n\x0c\n\x05\x04\x01\x02\x03\x03\x12\x03\r\x17\
-    \x18\n\x0b\n\x04\x04\x01\x02\x04\x12\x03\x0e\x04\x1b\n\x0c\n\x05\x04\x01\
-    \x02\x04\x06\x12\x03\x0e\x04\x0c\n\x0c\n\x05\x04\x01\x02\x04\x01\x12\x03\
-    \x0e\r\x16\n\x0c\n\x05\x04\x01\x02\x04\x03\x12\x03\x0e\x19\x1a\n\n\n\x02\
-    \x04\x02\x12\x04\x10\0\x1a\x01\n\n\n\x03\x04\x02\x01\x12\x03\x10\x08\x0c\
-    \n\x0b\n\x04\x04\x02\x02\0\x12\x03\x11\x04\x12\n\x0c\n\x05\x04\x02\x02\0\
-    \x05\x12\x03\x11\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x11\x0b\r\n\
-    \x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x11\x10\x11\n\x0b\n\x04\x04\x02\x02\
-    \x01\x12\x03\x12\x04\x1c\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x12\x04\
-    \n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x12\x0b\x17\n\x0c\n\x05\x04\
-    \x02\x02\x01\x03\x12\x03\x12\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\
-    \x13\x04\x14\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x13\x04\n\n\x0c\n\
-    \x05\x04\x02\x02\x02\x01\x12\x03\x13\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x02\
-    \x03\x12\x03\x13\x12\x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x14\x04\x14\
-    \n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\x14\x04\n\n\x0c\n\x05\x04\x02\
-    \x02\x03\x01\x12\x03\x14\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\
-    \x14\x12\x13\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x15\x04\x1b\n\x0c\n\x05\
-    \x04\x02\x02\x04\x06\x12\x03\x15\x04\x0c\n\x0c\n\x05\x04\x02\x02\x04\x01\
-    \x12\x03\x15\r\x16\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x15\x19\x1a\n\
-    \x0b\n\x04\x04\x02\x02\x05\x12\x03\x16\x04\x16\n\x0c\n\x05\x04\x02\x02\
-    \x05\x05\x12\x03\x16\x04\t\n\x0c\n\x05\x04\x02\x02\x05\x01\x12\x03\x16\n\
-    \x11\n\x0c\n\x05\x04\x02\x02\x05\x03\x12\x03\x16\x14\x15\n\x0b\n\x04\x04\
-    \x02\x02\x06\x12\x03\x17\x04\x20\n\x0c\n\x05\x04\x02\x02\x06\x06\x12\x03\
-    \x17\x04\x10\n\x0c\n\x05\x04\x02\x02\x06\x01\x12\x03\x17\x11\x1b\n\x0c\n\
-    \x05\x04\x02\x02\x06\x03\x12\x03\x17\x1e\x1f\n\x0b\n\x04\x04\x02\x02\x07\
-    \x12\x03\x18\x04\x1c\n\x0c\n\x05\x04\x02\x02\x07\x05\x12\x03\x18\x04\t\n\
-    \x0c\n\x05\x04\x02\x02\x07\x01\x12\x03\x18\n\x17\n\x0c\n\x05\x04\x02\x02\
-    \x07\x03\x12\x03\x18\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x08\x12\x03\x19\x04\
-    \x1a\n\x0c\n\x05\x04\x02\x02\x08\x05\x12\x03\x19\x04\t\n\x0c\n\x05\x04\
-    \x02\x02\x08\x01\x12\x03\x19\n\x15\n\x0c\n\x05\x04\x02\x02\x08\x03\x12\
-    \x03\x19\x18\x19\n\n\n\x02\x04\x03\x12\x04\x1b\0\x1d\x01\n\n\n\x03\x04\
-    \x03\x01\x12\x03\x1b\x08\x14\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x1c\x04\
-    \x1c\n\x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x1c\x04\x0c\n\x0c\n\x05\x04\
-    \x03\x02\0\x06\x12\x03\x1c\r\x11\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\
-    \x1c\x12\x17\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x1c\x1a\x1b\n\n\n\x02\
-    \x05\0\x12\x04\x1e\0!\x01\n\n\n\x03\x05\0\x01\x12\x03\x1e\x05\r\n\x0b\n\
-    \x04\x05\0\x02\0\x12\x03\x1f\x04\x0e\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\
-    \x1f\x04\t\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x1f\x0c\r\n\x0b\n\x04\x05\
-    \0\x02\x01\x12\x03\x20\x04\x0c\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x20\
-    \x04\x07\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x20\n\x0bb\x06proto3\
+    Type\x12\x1b\n\tview_data\x18\x06\x20\x01(\tR\x08viewData\x12\x17\n\x07v\
+    iew_id\x18\x07\x20\x01(\tR\x06viewId\"\x97\x02\n\x04View\x12\x0e\n\x02id\
+    \x18\x01\x20\x01(\tR\x02id\x12\x20\n\x0cbelong_to_id\x18\x02\x20\x01(\tR\
+    \nbelongToId\x12\x12\n\x04name\x18\x03\x20\x01(\tR\x04name\x12\x12\n\x04\
+    desc\x18\x04\x20\x01(\tR\x04desc\x12&\n\tview_type\x18\x05\x20\x01(\x0e2\
+    \t.ViewTypeR\x08viewType\x12\x18\n\x07version\x18\x06\x20\x01(\x03R\x07v\
+    ersion\x12-\n\nbelongings\x18\x07\x20\x01(\x0b2\r.RepeatedViewR\nbelongi\
+    ngs\x12#\n\rmodified_time\x18\x08\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\
+    \n\x0bcreate_time\x18\t\x20\x01(\x03R\ncreateTime\"+\n\x0cRepeatedView\
+    \x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.ViewR\x05items*\x1e\n\x08V\
+    iewType\x12\t\n\x05Blank\x10\0\x12\x07\n\x03Doc\x10\x01J\xbf\x0b\n\x06\
+    \x12\x04\0\0#\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\
+    \x04\x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x19\n\x0b\n\x04\
+    \x04\0\x02\0\x12\x03\x03\x04\x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\
+    \x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x17\n\x0c\n\x05\x04\0\
+    \x02\0\x03\x12\x03\x03\x1a\x1b\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\
+    \x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\
+    \x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\
+    \x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\
+    \x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\
+    \x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\
+    \n\x04\x04\0\x08\0\x12\x03\x06\x044\n\x0c\n\x05\x04\0\x08\0\x01\x12\x03\
+    \x06\n\x1a\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x1d2\n\x0c\n\x05\x04\0\
+    \x02\x03\x05\x12\x03\x06\x1d#\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06$\
+    -\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x0601\n\x0b\n\x04\x04\0\x02\x04\
+    \x12\x03\x07\x04\x1b\n\x0c\n\x05\x04\0\x02\x04\x06\x12\x03\x07\x04\x0c\n\
+    \x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07\r\x16\n\x0c\n\x05\x04\0\x02\x04\
+    \x03\x12\x03\x07\x19\x1a\n\n\n\x02\x04\x01\x12\x04\t\0\x11\x01\n\n\n\x03\
+    \x04\x01\x01\x12\x03\t\x08\x18\n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\
+    \x1c\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\
+    \x02\0\x01\x12\x03\n\x0b\x17\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x1a\
+    \x1b\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x04\x01\
+    \x02\x01\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\
+    \x0b\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x0b\x12\x13\n\x0b\n\
+    \x04\x04\x01\x02\x02\x12\x03\x0c\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\
+    \x12\x03\x0c\x04\n\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\x0c\x0b\x0f\n\
+    \x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x01\
+    \x02\x03\x12\x03\r\x04\x19\n\x0c\n\x05\x04\x01\x02\x03\x05\x12\x03\r\x04\
+    \n\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\r\x0b\x14\n\x0c\n\x05\x04\x01\
+    \x02\x03\x03\x12\x03\r\x17\x18\n\x0b\n\x04\x04\x01\x02\x04\x12\x03\x0e\
+    \x04\x1b\n\x0c\n\x05\x04\x01\x02\x04\x06\x12\x03\x0e\x04\x0c\n\x0c\n\x05\
+    \x04\x01\x02\x04\x01\x12\x03\x0e\r\x16\n\x0c\n\x05\x04\x01\x02\x04\x03\
+    \x12\x03\x0e\x19\x1a\n\x0b\n\x04\x04\x01\x02\x05\x12\x03\x0f\x04\x19\n\
+    \x0c\n\x05\x04\x01\x02\x05\x05\x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x01\x02\
+    \x05\x01\x12\x03\x0f\x0b\x14\n\x0c\n\x05\x04\x01\x02\x05\x03\x12\x03\x0f\
+    \x17\x18\n\x0b\n\x04\x04\x01\x02\x06\x12\x03\x10\x04\x17\n\x0c\n\x05\x04\
+    \x01\x02\x06\x05\x12\x03\x10\x04\n\n\x0c\n\x05\x04\x01\x02\x06\x01\x12\
+    \x03\x10\x0b\x12\n\x0c\n\x05\x04\x01\x02\x06\x03\x12\x03\x10\x15\x16\n\n\
+    \n\x02\x04\x02\x12\x04\x12\0\x1c\x01\n\n\n\x03\x04\x02\x01\x12\x03\x12\
+    \x08\x0c\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x13\x04\x12\n\x0c\n\x05\x04\
+    \x02\x02\0\x05\x12\x03\x13\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\
+    \x13\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x13\x10\x11\n\x0b\n\x04\
+    \x04\x02\x02\x01\x12\x03\x14\x04\x1c\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\
+    \x03\x14\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x14\x0b\x17\n\x0c\
+    \n\x05\x04\x02\x02\x01\x03\x12\x03\x14\x1a\x1b\n\x0b\n\x04\x04\x02\x02\
+    \x02\x12\x03\x15\x04\x14\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x15\x04\
+    \n\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x15\x0b\x0f\n\x0c\n\x05\x04\
+    \x02\x02\x02\x03\x12\x03\x15\x12\x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\
+    \x16\x04\x14\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\x16\x04\n\n\x0c\n\
+    \x05\x04\x02\x02\x03\x01\x12\x03\x16\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x03\
+    \x03\x12\x03\x16\x12\x13\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x17\x04\x1b\
+    \n\x0c\n\x05\x04\x02\x02\x04\x06\x12\x03\x17\x04\x0c\n\x0c\n\x05\x04\x02\
+    \x02\x04\x01\x12\x03\x17\r\x16\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\
+    \x17\x19\x1a\n\x0b\n\x04\x04\x02\x02\x05\x12\x03\x18\x04\x16\n\x0c\n\x05\
+    \x04\x02\x02\x05\x05\x12\x03\x18\x04\t\n\x0c\n\x05\x04\x02\x02\x05\x01\
+    \x12\x03\x18\n\x11\n\x0c\n\x05\x04\x02\x02\x05\x03\x12\x03\x18\x14\x15\n\
+    \x0b\n\x04\x04\x02\x02\x06\x12\x03\x19\x04\x20\n\x0c\n\x05\x04\x02\x02\
+    \x06\x06\x12\x03\x19\x04\x10\n\x0c\n\x05\x04\x02\x02\x06\x01\x12\x03\x19\
+    \x11\x1b\n\x0c\n\x05\x04\x02\x02\x06\x03\x12\x03\x19\x1e\x1f\n\x0b\n\x04\
+    \x04\x02\x02\x07\x12\x03\x1a\x04\x1c\n\x0c\n\x05\x04\x02\x02\x07\x05\x12\
+    \x03\x1a\x04\t\n\x0c\n\x05\x04\x02\x02\x07\x01\x12\x03\x1a\n\x17\n\x0c\n\
+    \x05\x04\x02\x02\x07\x03\x12\x03\x1a\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x08\
+    \x12\x03\x1b\x04\x1a\n\x0c\n\x05\x04\x02\x02\x08\x05\x12\x03\x1b\x04\t\n\
+    \x0c\n\x05\x04\x02\x02\x08\x01\x12\x03\x1b\n\x15\n\x0c\n\x05\x04\x02\x02\
+    \x08\x03\x12\x03\x1b\x18\x19\n\n\n\x02\x04\x03\x12\x04\x1d\0\x1f\x01\n\n\
+    \n\x03\x04\x03\x01\x12\x03\x1d\x08\x14\n\x0b\n\x04\x04\x03\x02\0\x12\x03\
+    \x1e\x04\x1c\n\x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x1e\x04\x0c\n\x0c\n\
+    \x05\x04\x03\x02\0\x06\x12\x03\x1e\r\x11\n\x0c\n\x05\x04\x03\x02\0\x01\
+    \x12\x03\x1e\x12\x17\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x1e\x1a\x1b\n\
+    \n\n\x02\x05\0\x12\x04\x20\0#\x01\n\n\n\x03\x05\0\x01\x12\x03\x20\x05\r\
+    \n\x0b\n\x04\x05\0\x02\0\x12\x03!\x04\x0e\n\x0c\n\x05\x05\0\x02\0\x01\
+    \x12\x03!\x04\t\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03!\x0c\r\n\x0b\n\x04\
+    \x05\0\x02\x01\x12\x03\"\x04\x0c\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\"\
+    \x04\x07\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\"\n\x0bb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 0
shared-lib/flowy-core-data-model/src/protobuf/proto/view_create.proto

@@ -13,6 +13,8 @@ message CreateViewParams {
     string desc = 3;
     string thumbnail = 4;
     ViewType view_type = 5;
+    string view_data = 6;
+    string view_id = 7;
 }
 message View {
     string id = 1;

+ 1 - 1
shared-lib/lib-infra/src/lib.rs

@@ -2,7 +2,7 @@ pub mod future;
 pub mod retry;
 
 #[allow(dead_code)]
-pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }
+pub fn uuid_string() -> String { uuid::Uuid::new_v4().to_string() }
 
 #[allow(dead_code)]
 pub fn timestamp() -> i64 { chrono::Utc::now().timestamp() }