瀏覽代碼

mock workspace api

appflowy 3 年之前
父節點
當前提交
9d667b6c96
共有 32 個文件被更改,包括 748 次插入316 次删除
  1. 28 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pb.dart
  2. 3 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pbjson.dart
  3. 28 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pb.dart
  4. 3 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pbjson.dart
  5. 29 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart
  6. 3 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart
  7. 2 0
      backend/src/workspace_service/app/builder.rs
  8. 2 0
      backend/src/workspace_service/view/builder.rs
  9. 2 0
      backend/src/workspace_service/workspace/builder.rs
  10. 8 2
      rust-lib/flowy-workspace/src/entities/app/app_create.rs
  11. 1 1
      rust-lib/flowy-workspace/src/entities/app/app_query.rs
  12. 3 11
      rust-lib/flowy-workspace/src/entities/app/app_update.rs
  13. 10 8
      rust-lib/flowy-workspace/src/entities/view/view_create.rs
  14. 1 1
      rust-lib/flowy-workspace/src/entities/view/view_query.rs
  15. 2 6
      rust-lib/flowy-workspace/src/entities/view/view_update.rs
  16. 6 12
      rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs
  17. 2 2
      rust-lib/flowy-workspace/src/handlers/app_handler.rs
  18. 1 1
      rust-lib/flowy-workspace/src/handlers/view_handler.rs
  19. 1 1
      rust-lib/flowy-workspace/src/module.rs
  20. 132 55
      rust-lib/flowy-workspace/src/protobuf/model/app_create.rs
  21. 143 66
      rust-lib/flowy-workspace/src/protobuf/model/view_create.rs
  22. 114 36
      rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs
  23. 2 0
      rust-lib/flowy-workspace/src/protobuf/proto/app_create.proto
  24. 2 0
      rust-lib/flowy-workspace/src/protobuf/proto/view_create.proto
  25. 2 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_create.proto
  26. 64 20
      rust-lib/flowy-workspace/src/services/app_controller.rs
  27. 10 1
      rust-lib/flowy-workspace/src/services/server/server_api_mock.rs
  28. 73 8
      rust-lib/flowy-workspace/src/services/view_controller.rs
  29. 23 21
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  30. 14 18
      rust-lib/flowy-workspace/src/sql_tables/app/app_table.rs
  31. 14 13
      rust-lib/flowy-workspace/src/sql_tables/view/view_table.rs
  32. 20 30
      rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_table.rs

+ 28 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pb.dart

@@ -249,6 +249,8 @@ class App extends $pb.GeneratedMessage {
     ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
     ..aOM<$0.RepeatedView>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: $0.RepeatedView.create)
     ..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'version')
+    ..aInt64(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
+    ..aInt64(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
     ..hasRequiredFields = false
   ;
 
@@ -260,6 +262,8 @@ class App extends $pb.GeneratedMessage {
     $core.String? desc,
     $0.RepeatedView? belongings,
     $fixnum.Int64? version,
+    $fixnum.Int64? modifiedTime,
+    $fixnum.Int64? createTime,
   }) {
     final _result = create();
     if (id != null) {
@@ -280,6 +284,12 @@ class App extends $pb.GeneratedMessage {
     if (version != null) {
       _result.version = version;
     }
+    if (modifiedTime != null) {
+      _result.modifiedTime = modifiedTime;
+    }
+    if (createTime != null) {
+      _result.createTime = createTime;
+    }
     return _result;
   }
   factory App.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -358,6 +368,24 @@ class App extends $pb.GeneratedMessage {
   $core.bool hasVersion() => $_has(5);
   @$pb.TagNumber(6)
   void clearVersion() => clearField(6);
+
+  @$pb.TagNumber(7)
+  $fixnum.Int64 get modifiedTime => $_getI64(6);
+  @$pb.TagNumber(7)
+  set modifiedTime($fixnum.Int64 v) { $_setInt64(6, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasModifiedTime() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearModifiedTime() => clearField(7);
+
+  @$pb.TagNumber(8)
+  $fixnum.Int64 get createTime => $_getI64(7);
+  @$pb.TagNumber(8)
+  set createTime($fixnum.Int64 v) { $_setInt64(7, v); }
+  @$pb.TagNumber(8)
+  $core.bool hasCreateTime() => $_has(7);
+  @$pb.TagNumber(8)
+  void clearCreateTime() => clearField(8);
 }
 
 class RepeatedApp extends $pb.GeneratedMessage {

+ 3 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pbjson.dart

@@ -54,11 +54,13 @@ const App$json = const {
     const {'1': 'desc', '3': 4, '4': 1, '5': 9, '10': 'desc'},
     const {'1': 'belongings', '3': 5, '4': 1, '5': 11, '6': '.RepeatedView', '10': 'belongings'},
     const {'1': 'version', '3': 6, '4': 1, '5': 3, '10': 'version'},
+    const {'1': 'modified_time', '3': 7, '4': 1, '5': 3, '10': 'modifiedTime'},
+    const {'1': 'create_time', '3': 8, '4': 1, '5': 3, '10': 'createTime'},
   ],
 };
 
 /// Descriptor for `App`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List appDescriptor = $convert.base64Decode('CgNBcHASDgoCaWQYASABKAlSAmlkEiEKDHdvcmtzcGFjZV9pZBgCIAEoCVILd29ya3NwYWNlSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEi0KCmJlbG9uZ2luZ3MYBSABKAsyDS5SZXBlYXRlZFZpZXdSCmJlbG9uZ2luZ3MSGAoHdmVyc2lvbhgGIAEoA1IHdmVyc2lvbg==');
+final $typed_data.Uint8List appDescriptor = $convert.base64Decode('CgNBcHASDgoCaWQYASABKAlSAmlkEiEKDHdvcmtzcGFjZV9pZBgCIAEoCVILd29ya3NwYWNlSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEi0KCmJlbG9uZ2luZ3MYBSABKAsyDS5SZXBlYXRlZFZpZXdSCmJlbG9uZ2luZ3MSGAoHdmVyc2lvbhgGIAEoA1IHdmVyc2lvbhIjCg1tb2RpZmllZF90aW1lGAcgASgDUgxtb2RpZmllZFRpbWUSHwoLY3JlYXRlX3RpbWUYCCABKANSCmNyZWF0ZVRpbWU=');
 @$core.Deprecated('Use repeatedAppDescriptor instead')
 const RepeatedApp$json = const {
   '1': 'RepeatedApp',

+ 28 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pb.dart

@@ -242,6 +242,8 @@ class View extends $pb.GeneratedMessage {
     ..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
     ..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'version')
     ..aOM<RepeatedView>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: RepeatedView.create)
+    ..aInt64(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
+    ..aInt64(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
     ..hasRequiredFields = false
   ;
 
@@ -254,6 +256,8 @@ class View extends $pb.GeneratedMessage {
     ViewType? viewType,
     $fixnum.Int64? version,
     RepeatedView? belongings,
+    $fixnum.Int64? modifiedTime,
+    $fixnum.Int64? createTime,
   }) {
     final _result = create();
     if (id != null) {
@@ -277,6 +281,12 @@ class View extends $pb.GeneratedMessage {
     if (belongings != null) {
       _result.belongings = belongings;
     }
+    if (modifiedTime != null) {
+      _result.modifiedTime = modifiedTime;
+    }
+    if (createTime != null) {
+      _result.createTime = createTime;
+    }
     return _result;
   }
   factory View.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -364,6 +374,24 @@ class View extends $pb.GeneratedMessage {
   void clearBelongings() => clearField(7);
   @$pb.TagNumber(7)
   RepeatedView ensureBelongings() => $_ensure(6);
+
+  @$pb.TagNumber(8)
+  $fixnum.Int64 get modifiedTime => $_getI64(7);
+  @$pb.TagNumber(8)
+  set modifiedTime($fixnum.Int64 v) { $_setInt64(7, v); }
+  @$pb.TagNumber(8)
+  $core.bool hasModifiedTime() => $_has(7);
+  @$pb.TagNumber(8)
+  void clearModifiedTime() => clearField(8);
+
+  @$pb.TagNumber(9)
+  $fixnum.Int64 get createTime => $_getI64(8);
+  @$pb.TagNumber(9)
+  set createTime($fixnum.Int64 v) { $_setInt64(8, v); }
+  @$pb.TagNumber(9)
+  $core.bool hasCreateTime() => $_has(8);
+  @$pb.TagNumber(9)
+  void clearCreateTime() => clearField(9);
 }
 
 class RepeatedView extends $pb.GeneratedMessage {

+ 3 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pbjson.dart

@@ -61,11 +61,13 @@ const View$json = const {
     const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
     const {'1': 'version', '3': 6, '4': 1, '5': 3, '10': 'version'},
     const {'1': 'belongings', '3': 7, '4': 1, '5': 11, '6': '.RepeatedView', '10': 'belongings'},
+    const {'1': 'modified_time', '3': 8, '4': 1, '5': 3, '10': 'modifiedTime'},
+    const {'1': 'create_time', '3': 9, '4': 1, '5': 3, '10': 'createTime'},
   ],
 };
 
 /// Descriptor for `View`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEiYKCXZpZXdfdHlwZRgFIAEoDjIJLlZpZXdUeXBlUgh2aWV3VHlwZRIYCgd2ZXJzaW9uGAYgASgDUgd2ZXJzaW9uEi0KCmJlbG9uZ2luZ3MYByABKAsyDS5SZXBlYXRlZFZpZXdSCmJlbG9uZ2luZ3M=');
+final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEiYKCXZpZXdfdHlwZRgFIAEoDjIJLlZpZXdUeXBlUgh2aWV3VHlwZRIYCgd2ZXJzaW9uGAYgASgDUgd2ZXJzaW9uEi0KCmJlbG9uZ2luZ3MYByABKAsyDS5SZXBlYXRlZFZpZXdSCmJlbG9uZ2luZ3MSIwoNbW9kaWZpZWRfdGltZRgIIAEoA1IMbW9kaWZpZWRUaW1lEh8KC2NyZWF0ZV90aW1lGAkgASgDUgpjcmVhdGVUaW1l');
 @$core.Deprecated('Use repeatedViewDescriptor instead')
 const RepeatedView$json = const {
   '1': 'RepeatedView',

+ 29 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart

@@ -7,6 +7,7 @@
 
 import 'dart:core' as $core;
 
+import 'package:fixnum/fixnum.dart' as $fixnum;
 import 'package:protobuf/protobuf.dart' as $pb;
 
 import 'app_create.pb.dart' as $0;
@@ -139,6 +140,8 @@ class Workspace extends $pb.GeneratedMessage {
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
     ..aOM<$0.RepeatedApp>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'apps', subBuilder: $0.RepeatedApp.create)
+    ..aInt64(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
+    ..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
     ..hasRequiredFields = false
   ;
 
@@ -148,6 +151,8 @@ class Workspace extends $pb.GeneratedMessage {
     $core.String? name,
     $core.String? desc,
     $0.RepeatedApp? apps,
+    $fixnum.Int64? modifiedTime,
+    $fixnum.Int64? createTime,
   }) {
     final _result = create();
     if (id != null) {
@@ -162,6 +167,12 @@ class Workspace extends $pb.GeneratedMessage {
     if (apps != null) {
       _result.apps = apps;
     }
+    if (modifiedTime != null) {
+      _result.modifiedTime = modifiedTime;
+    }
+    if (createTime != null) {
+      _result.createTime = createTime;
+    }
     return _result;
   }
   factory Workspace.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -222,6 +233,24 @@ class Workspace extends $pb.GeneratedMessage {
   void clearApps() => clearField(4);
   @$pb.TagNumber(4)
   $0.RepeatedApp ensureApps() => $_ensure(3);
+
+  @$pb.TagNumber(5)
+  $fixnum.Int64 get modifiedTime => $_getI64(4);
+  @$pb.TagNumber(5)
+  set modifiedTime($fixnum.Int64 v) { $_setInt64(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasModifiedTime() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearModifiedTime() => clearField(5);
+
+  @$pb.TagNumber(6)
+  $fixnum.Int64 get createTime => $_getI64(5);
+  @$pb.TagNumber(6)
+  set createTime($fixnum.Int64 v) { $_setInt64(5, v); }
+  @$pb.TagNumber(6)
+  $core.bool hasCreateTime() => $_has(5);
+  @$pb.TagNumber(6)
+  void clearCreateTime() => clearField(6);
 }
 
 class RepeatedWorkspace extends $pb.GeneratedMessage {

+ 3 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart

@@ -38,11 +38,13 @@ const Workspace$json = const {
     const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
     const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
     const {'1': 'apps', '3': 4, '4': 1, '5': 11, '6': '.RepeatedApp', '10': 'apps'},
+    const {'1': 'modified_time', '3': 5, '4': 1, '5': 3, '10': 'modifiedTime'},
+    const {'1': 'create_time', '3': 6, '4': 1, '5': 3, '10': 'createTime'},
   ],
 };
 
 /// Descriptor for `Workspace`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIgCgRhcHBzGAQgASgLMgwuUmVwZWF0ZWRBcHBSBGFwcHM=');
+final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIgCgRhcHBzGAQgASgLMgwuUmVwZWF0ZWRBcHBSBGFwcHMSIwoNbW9kaWZpZWRfdGltZRgFIAEoA1IMbW9kaWZpZWRUaW1lEh8KC2NyZWF0ZV90aW1lGAYgASgDUgpjcmVhdGVUaW1l');
 @$core.Deprecated('Use repeatedWorkspaceDescriptor instead')
 const RepeatedWorkspace$json = const {
   '1': 'RepeatedWorkspace',

+ 2 - 0
backend/src/workspace_service/app/builder.rs

@@ -92,6 +92,8 @@ pub(crate) fn make_app_from_table(table: AppTable, views: RepeatedView) -> App {
     app.set_name(table.name.clone());
     app.set_desc(table.description.clone());
     app.set_belongings(views);
+    app.set_modified_time(table.modified_time.timestamp());
+    app.set_create_time(table.create_time.timestamp());
 
     app
 }

+ 2 - 0
backend/src/workspace_service/view/builder.rs

@@ -81,6 +81,8 @@ pub(crate) fn make_view_from_table(table: ViewTable, views: RepeatedView) -> Vie
     view.set_desc(table.description);
     view.set_view_type(view_type);
     view.set_belongings(views);
+    view.set_create_time(table.create_time.timestamp());
+    view.set_modified_time(table.modified_time.timestamp());
 
     view
 }

+ 2 - 0
backend/src/workspace_service/workspace/builder.rs

@@ -64,6 +64,8 @@ pub(crate) fn make_workspace_from_table(
         name: table.name,
         desc: table.description,
         apps: Default::default(),
+        modified_time: table.modified_time.timestamp(),
+        create_time: table.create_time.timestamp(),
         unknown_fields: Default::default(),
         cached_size: Default::default(),
     };

+ 8 - 2
rust-lib/flowy-workspace/src/entities/app/app_create.rs

@@ -67,7 +67,7 @@ impl TryInto<CreateAppParams> for CreateAppRequest {
     }
 }
 
-#[derive(PartialEq, ProtoBuf, Default, Debug)]
+#[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
 pub struct App {
     #[pb(index = 1)]
     pub id: String,
@@ -86,9 +86,15 @@ pub struct App {
 
     #[pb(index = 6)]
     pub version: i64,
+
+    #[pb(index = 7)]
+    pub modified_time: i64,
+
+    #[pb(index = 8)]
+    pub create_time: i64,
 }
 
-#[derive(PartialEq, Debug, Default, ProtoBuf)]
+#[derive(PartialEq, Debug, Default, ProtoBuf, Clone)]
 pub struct RepeatedApp {
     #[pb(index = 1)]
     pub items: Vec<App>,

+ 1 - 1
rust-lib/flowy-workspace/src/entities/app/app_query.rs

@@ -34,7 +34,7 @@ impl QueryAppRequest {
     }
 }
 
-#[derive(ProtoBuf, Default)]
+#[derive(ProtoBuf, Default, Clone)]
 pub struct QueryAppParams {
     #[pb(index = 1)]
     pub app_id: String,

+ 3 - 11
rust-lib/flowy-workspace/src/entities/app/app_update.rs

@@ -26,7 +26,7 @@ pub struct UpdateAppRequest {
     pub is_trash: Option<bool>,
 }
 
-#[derive(ProtoBuf, Default)]
+#[derive(ProtoBuf, Default, Clone)]
 pub struct UpdateAppParams {
     #[pb(index = 1)]
     pub app_id: String,
@@ -80,11 +80,7 @@ impl TryInto<UpdateAppParams> for UpdateAppRequest {
             None => None,
             Some(name) => Some(
                 AppName::parse(name)
-                    .map_err(|e| {
-                        ErrorBuilder::new(ErrorCode::WorkspaceNameInvalid)
-                            .msg(e)
-                            .build()
-                    })?
+                    .map_err(|e| ErrorBuilder::new(ErrorCode::WorkspaceNameInvalid).msg(e).build())?
                     .0,
             ),
         };
@@ -93,11 +89,7 @@ impl TryInto<UpdateAppParams> for UpdateAppRequest {
             None => None,
             Some(color_style) => Some(
                 AppColorStyle::parse(color_style)
-                    .map_err(|e| {
-                        ErrorBuilder::new(ErrorCode::AppColorStyleInvalid)
-                            .msg(e)
-                            .build()
-                    })?
+                    .map_err(|e| ErrorBuilder::new(ErrorCode::AppColorStyleInvalid).msg(e).build())?
                     .0,
             ),
         };

+ 10 - 8
rust-lib/flowy-workspace/src/entities/view/view_create.rs

@@ -6,7 +6,7 @@ use crate::{
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use std::convert::TryInto;
 
-#[derive(PartialEq, Debug, ProtoBuf_Enum)]
+#[derive(PartialEq, Debug, ProtoBuf_Enum, Clone)]
 pub enum ViewType {
     Blank = 0,
     Doc   = 1,
@@ -81,11 +81,7 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
             None => "".to_string(),
             Some(thumbnail) => {
                 ViewThumbnail::parse(thumbnail)
-                    .map_err(|e| {
-                        ErrorBuilder::new(ErrorCode::ViewThumbnailInvalid)
-                            .msg(e)
-                            .build()
-                    })?
+                    .map_err(|e| ErrorBuilder::new(ErrorCode::ViewThumbnailInvalid).msg(e).build())?
                     .0
             },
         };
@@ -100,7 +96,7 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
     }
 }
 
-#[derive(PartialEq, ProtoBuf, Default, Debug)]
+#[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
 pub struct View {
     #[pb(index = 1)]
     pub id: String,
@@ -122,9 +118,15 @@ pub struct View {
 
     #[pb(index = 7)]
     pub belongings: RepeatedView,
+
+    #[pb(index = 8)]
+    pub modified_time: i64,
+
+    #[pb(index = 9)]
+    pub create_time: i64,
 }
 
-#[derive(PartialEq, Debug, Default, ProtoBuf)]
+#[derive(PartialEq, Debug, Default, ProtoBuf, Clone)]
 pub struct RepeatedView {
     #[pb(index = 1)]
     pub items: Vec<View>,

+ 1 - 1
rust-lib/flowy-workspace/src/entities/view/view_query.rs

@@ -32,7 +32,7 @@ impl QueryViewRequest {
     }
 }
 
-#[derive(Default, ProtoBuf)]
+#[derive(Default, ProtoBuf, Clone)]
 pub struct QueryViewParams {
     #[pb(index = 1)]
     pub view_id: String,

+ 2 - 6
rust-lib/flowy-workspace/src/entities/view/view_update.rs

@@ -23,7 +23,7 @@ pub struct UpdateViewRequest {
     pub is_trash: Option<bool>,
 }
 
-#[derive(Default, ProtoBuf)]
+#[derive(Default, ProtoBuf, Clone)]
 pub struct UpdateViewParams {
     #[pb(index = 1)]
     pub view_id: String,
@@ -94,11 +94,7 @@ impl TryInto<UpdateViewParams> for UpdateViewRequest {
             None => None,
             Some(thumbnail) => Some(
                 ViewThumbnail::parse(thumbnail)
-                    .map_err(|e| {
-                        ErrorBuilder::new(ErrorCode::ViewThumbnailInvalid)
-                            .msg(e)
-                            .build()
-                    })?
+                    .map_err(|e| ErrorBuilder::new(ErrorCode::ViewThumbnailInvalid).msg(e).build())?
                     .0,
             ),
         };

+ 6 - 12
rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs

@@ -29,7 +29,6 @@ impl TryInto<CreateWorkspaceParams> for CreateWorkspaceRequest {
 
     fn try_into(self) -> Result<CreateWorkspaceParams, Self::Error> {
         let name = WorkspaceName::parse(self.name).map_err(|e| ErrorBuilder::new(ErrorCode::WorkspaceNameInvalid).msg(e).build())?;
-
         let desc = WorkspaceDesc::parse(self.desc).map_err(|e| ErrorBuilder::new(ErrorCode::WorkspaceDescInvalid).msg(e).build())?;
 
         Ok(CreateWorkspaceParams {
@@ -39,7 +38,7 @@ impl TryInto<CreateWorkspaceParams> for CreateWorkspaceRequest {
     }
 }
 
-#[derive(PartialEq, ProtoBuf, Default, Debug)]
+#[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
 pub struct Workspace {
     #[pb(index = 1)]
     pub id: String,
@@ -52,17 +51,12 @@ pub struct Workspace {
 
     #[pb(index = 4)]
     pub apps: RepeatedApp,
-}
 
-impl Workspace {
-    pub fn new(id: String, name: String, desc: String) -> Self {
-        Self {
-            id,
-            name,
-            desc,
-            apps: RepeatedApp::default(),
-        }
-    }
+    #[pb(index = 5)]
+    pub modified_time: i64,
+
+    #[pb(index = 6)]
+    pub create_time: i64,
 }
 
 #[derive(PartialEq, Debug, Default, ProtoBuf)]

+ 2 - 2
rust-lib/flowy-workspace/src/handlers/app_handler.rs

@@ -22,7 +22,7 @@ use std::{convert::TryInto, sync::Arc};
 #[tracing::instrument(name = "create_app", skip(data, controller))]
 pub(crate) async fn create_app(data: Data<CreateAppRequest>, controller: Unit<Arc<AppController>>) -> DataResult<App, WorkspaceError> {
     let params: CreateAppParams = data.into_inner().try_into()?;
-    let detail = controller.create_app(params)?;
+    let detail = controller.create_app(params).await?;
     data_result(detail)
 }
 
@@ -47,7 +47,7 @@ pub(crate) async fn read_app(
     view_controller: Unit<Arc<ViewController>>,
 ) -> DataResult<App, WorkspaceError> {
     let params: QueryAppParams = data.into_inner().try_into()?;
-    let mut app = app_controller.read_app(&params.app_id, params.is_trash).await?;
+    let mut app = app_controller.read_app(params.clone()).await?;
 
     // The View's belonging is the view indexed by the belong_to_id for now
     if params.read_belongings {

+ 1 - 1
rust-lib/flowy-workspace/src/handlers/view_handler.rs

@@ -27,7 +27,7 @@ pub(crate) async fn create_view(data: Data<CreateViewRequest>, controller: Unit<
 #[tracing::instrument(name = "read_view", skip(data, controller))]
 pub(crate) async fn read_view(data: Data<QueryViewRequest>, controller: Unit<Arc<ViewController>>) -> DataResult<View, WorkspaceError> {
     let params: QueryViewParams = data.into_inner().try_into()?;
-    let mut view = controller.read_view(&params.view_id, params.is_trash).await?;
+    let mut view = controller.read_view(params.clone()).await?;
 
     if params.read_belongings {
         let views = controller.read_views_belong_to(&params.view_id).await?;

+ 1 - 1
rust-lib/flowy-workspace/src/module.rs

@@ -26,7 +26,7 @@ pub trait WorkspaceDatabase: Send + Sync {
 
 pub fn create(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>) -> Module {
     let server = construct_workspace_server();
-    let view_controller = Arc::new(ViewController::new(database.clone(), server.clone()));
+    let view_controller = Arc::new(ViewController::new(user.clone(), database.clone(), server.clone()));
 
     let app_controller = Arc::new(AppController::new(
         user.clone(),

+ 132 - 55
rust-lib/flowy-workspace/src/protobuf/model/app_create.rs

@@ -791,6 +791,8 @@ pub struct App {
     pub desc: ::std::string::String,
     pub belongings: ::protobuf::SingularPtrField<super::view_create::RepeatedView>,
     pub version: i64,
+    pub modified_time: i64,
+    pub create_time: i64,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -958,6 +960,36 @@ impl App {
     pub fn set_version(&mut self, v: i64) {
         self.version = v;
     }
+
+    // int64 modified_time = 7;
+
+
+    pub fn get_modified_time(&self) -> i64 {
+        self.modified_time
+    }
+    pub fn clear_modified_time(&mut self) {
+        self.modified_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_modified_time(&mut self, v: i64) {
+        self.modified_time = v;
+    }
+
+    // int64 create_time = 8;
+
+
+    pub fn get_create_time(&self) -> i64 {
+        self.create_time
+    }
+    pub fn clear_create_time(&mut self) {
+        self.create_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_create_time(&mut self, v: i64) {
+        self.create_time = v;
+    }
 }
 
 impl ::protobuf::Message for App {
@@ -996,6 +1028,20 @@ impl ::protobuf::Message for App {
                     let tmp = is.read_int64()?;
                     self.version = tmp;
                 },
+                7 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.modified_time = tmp;
+                },
+                8 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.create_time = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -1027,6 +1073,12 @@ impl ::protobuf::Message for App {
         if self.version != 0 {
             my_size += ::protobuf::rt::value_size(6, self.version, ::protobuf::wire_format::WireTypeVarint);
         }
+        if self.modified_time != 0 {
+            my_size += ::protobuf::rt::value_size(7, self.modified_time, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if self.create_time != 0 {
+            my_size += ::protobuf::rt::value_size(8, self.create_time, ::protobuf::wire_format::WireTypeVarint);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -1053,6 +1105,12 @@ impl ::protobuf::Message for App {
         if self.version != 0 {
             os.write_int64(6, self.version)?;
         }
+        if self.modified_time != 0 {
+            os.write_int64(7, self.modified_time)?;
+        }
+        if self.create_time != 0 {
+            os.write_int64(8, self.create_time)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -1121,6 +1179,16 @@ impl ::protobuf::Message for App {
                 |m: &App| { &m.version },
                 |m: &mut App| { &mut m.version },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "modified_time",
+                |m: &App| { &m.modified_time },
+                |m: &mut App| { &mut m.modified_time },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "create_time",
+                |m: &App| { &m.create_time },
+                |m: &mut App| { &mut m.create_time },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<App>(
                 "App",
                 fields,
@@ -1143,6 +1211,8 @@ impl ::protobuf::Clear for App {
         self.desc.clear();
         self.belongings.clear();
         self.version = 0;
+        self.modified_time = 0;
+        self.create_time = 0;
         self.unknown_fields.clear();
     }
 }
@@ -1334,65 +1404,72 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x01(\tR\nthemeColor\"\x8a\x01\n\x0fCreateAppParams\x12!\n\x0cworkspace_\
     id\x18\x01\x20\x01(\tR\x0bworkspaceId\x12\x12\n\x04name\x18\x02\x20\x01(\
     \tR\x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12,\n\x0bcolo\
-    r_style\x18\x04\x20\x01(\x0b2\x0b.ColorStyleR\ncolorStyle\"\xa9\x01\n\
+    r_style\x18\x04\x20\x01(\x0b2\x0b.ColorStyleR\ncolorStyle\"\xef\x01\n\
     \x03App\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12!\n\x0cworkspace_id\
     \x18\x02\x20\x01(\tR\x0bworkspaceId\x12\x12\n\x04name\x18\x03\x20\x01(\t\
     R\x04name\x12\x12\n\x04desc\x18\x04\x20\x01(\tR\x04desc\x12-\n\nbelongin\
     gs\x18\x05\x20\x01(\x0b2\r.RepeatedViewR\nbelongings\x12\x18\n\x07versio\
-    n\x18\x06\x20\x01(\x03R\x07version\")\n\x0bRepeatedApp\x12\x1a\n\x05item\
-    s\x18\x01\x20\x03(\x0b2\x04.AppR\x05itemsJ\x93\x08\n\x06\x12\x04\0\0\x1c\
-    \x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1b\n\
-    \n\n\x02\x04\0\x12\x04\x03\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\
-    \x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x04\x04\x1c\n\x0c\n\x05\x04\0\x02\0\
-    \x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x17\n\
-    \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x04\x1a\x1b\n\x0b\n\x04\x04\0\x02\x01\
-    \x12\x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\
-    \x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\
-    \x01\x03\x12\x03\x05\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x06\x04\
-    \x14\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x06\x04\n\n\x0c\n\x05\x04\0\
-    \x02\x02\x01\x12\x03\x06\x0b\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\
-    \x06\x12\x13\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x07\x04\x1f\n\x0c\n\x05\
-    \x04\0\x02\x03\x06\x12\x03\x07\x04\x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\
-    \x03\x07\x0f\x1a\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x07\x1d\x1e\n\n\n\
-    \x02\x04\x01\x12\x04\t\0\x0b\x01\n\n\n\x03\x04\x01\x01\x12\x03\t\x08\x12\
-    \n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\x1b\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\x16\n\
-    \x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x19\x1a\n\n\n\x02\x04\x02\x12\x04\
-    \x0c\0\x11\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0c\x08\x17\n\x0b\n\x04\x04\
-    \x02\x02\0\x12\x03\r\x04\x1c\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\r\x04\
-    \n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\r\x0b\x17\n\x0c\n\x05\x04\x02\
-    \x02\0\x03\x12\x03\r\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x0e\x04\
-    \x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\
-    \x02\x02\x01\x01\x12\x03\x0e\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\
-    \x03\x0e\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x0f\x04\x14\n\x0c\n\
-    \x05\x04\x02\x02\x02\x05\x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\x02\
-    \x01\x12\x03\x0f\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\x0f\x12\
-    \x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x10\x04\x1f\n\x0c\n\x05\x04\x02\
-    \x02\x03\x06\x12\x03\x10\x04\x0e\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\
-    \x10\x0f\x1a\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x10\x1d\x1e\n\n\n\
-    \x02\x04\x03\x12\x04\x12\0\x19\x01\n\n\n\x03\x04\x03\x01\x12\x03\x12\x08\
-    \x0b\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x13\x04\x12\n\x0c\n\x05\x04\x03\
-    \x02\0\x05\x12\x03\x13\x04\n\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x13\
-    \x0b\r\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x13\x10\x11\n\x0b\n\x04\x04\
-    \x03\x02\x01\x12\x03\x14\x04\x1c\n\x0c\n\x05\x04\x03\x02\x01\x05\x12\x03\
-    \x14\x04\n\n\x0c\n\x05\x04\x03\x02\x01\x01\x12\x03\x14\x0b\x17\n\x0c\n\
-    \x05\x04\x03\x02\x01\x03\x12\x03\x14\x1a\x1b\n\x0b\n\x04\x04\x03\x02\x02\
-    \x12\x03\x15\x04\x14\n\x0c\n\x05\x04\x03\x02\x02\x05\x12\x03\x15\x04\n\n\
-    \x0c\n\x05\x04\x03\x02\x02\x01\x12\x03\x15\x0b\x0f\n\x0c\n\x05\x04\x03\
-    \x02\x02\x03\x12\x03\x15\x12\x13\n\x0b\n\x04\x04\x03\x02\x03\x12\x03\x16\
-    \x04\x14\n\x0c\n\x05\x04\x03\x02\x03\x05\x12\x03\x16\x04\n\n\x0c\n\x05\
-    \x04\x03\x02\x03\x01\x12\x03\x16\x0b\x0f\n\x0c\n\x05\x04\x03\x02\x03\x03\
-    \x12\x03\x16\x12\x13\n\x0b\n\x04\x04\x03\x02\x04\x12\x03\x17\x04\x20\n\
-    \x0c\n\x05\x04\x03\x02\x04\x06\x12\x03\x17\x04\x10\n\x0c\n\x05\x04\x03\
-    \x02\x04\x01\x12\x03\x17\x11\x1b\n\x0c\n\x05\x04\x03\x02\x04\x03\x12\x03\
-    \x17\x1e\x1f\n\x0b\n\x04\x04\x03\x02\x05\x12\x03\x18\x04\x16\n\x0c\n\x05\
-    \x04\x03\x02\x05\x05\x12\x03\x18\x04\t\n\x0c\n\x05\x04\x03\x02\x05\x01\
-    \x12\x03\x18\n\x11\n\x0c\n\x05\x04\x03\x02\x05\x03\x12\x03\x18\x14\x15\n\
-    \n\n\x02\x04\x04\x12\x04\x1a\0\x1c\x01\n\n\n\x03\x04\x04\x01\x12\x03\x1a\
-    \x08\x13\n\x0b\n\x04\x04\x04\x02\0\x12\x03\x1b\x04\x1b\n\x0c\n\x05\x04\
-    \x04\x02\0\x04\x12\x03\x1b\x04\x0c\n\x0c\n\x05\x04\x04\x02\0\x06\x12\x03\
-    \x1b\r\x10\n\x0c\n\x05\x04\x04\x02\0\x01\x12\x03\x1b\x11\x16\n\x0c\n\x05\
-    \x04\x04\x02\0\x03\x12\x03\x1b\x19\x1ab\x06proto3\
+    n\x18\x06\x20\x01(\x03R\x07version\x12#\n\rmodified_time\x18\x07\x20\x01\
+    (\x03R\x0cmodifiedTime\x12\x1f\n\x0bcreate_time\x18\x08\x20\x01(\x03R\nc\
+    reateTime\")\n\x0bRepeatedApp\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\
+    \x04.AppR\x05itemsJ\x81\t\n\x06\x12\x04\0\0\x1e\x01\n\x08\n\x01\x0c\x12\
+    \x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1b\n\n\n\x02\x04\0\x12\x04\
+    \x03\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\x18\n\x0b\n\x04\x04\0\
+    \x02\0\x12\x03\x04\x04\x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x04\x04\n\
+    \n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x17\n\x0c\n\x05\x04\0\x02\0\
+    \x03\x12\x03\x04\x1a\x1b\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x05\x04\x14\n\
+    \x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x01\
+    \x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x05\x12\
+    \x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x06\x04\x14\n\x0c\n\x05\x04\0\x02\
+    \x02\x05\x12\x03\x06\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x06\x0b\
+    \x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x06\x12\x13\n\x0b\n\x04\x04\0\
+    \x02\x03\x12\x03\x07\x04\x1f\n\x0c\n\x05\x04\0\x02\x03\x06\x12\x03\x07\
+    \x04\x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x07\x0f\x1a\n\x0c\n\x05\
+    \x04\0\x02\x03\x03\x12\x03\x07\x1d\x1e\n\n\n\x02\x04\x01\x12\x04\t\0\x0b\
+    \x01\n\n\n\x03\x04\x01\x01\x12\x03\t\x08\x12\n\x0b\n\x04\x04\x01\x02\0\
+    \x12\x03\n\x04\x1b\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\x16\n\x0c\n\x05\x04\x01\x02\0\x03\
+    \x12\x03\n\x19\x1a\n\n\n\x02\x04\x02\x12\x04\x0c\0\x11\x01\n\n\n\x03\x04\
+    \x02\x01\x12\x03\x0c\x08\x17\n\x0b\n\x04\x04\x02\x02\0\x12\x03\r\x04\x1c\
+    \n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x02\x02\0\
+    \x01\x12\x03\r\x0b\x17\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\r\x1a\x1b\n\
+    \x0b\n\x04\x04\x02\x02\x01\x12\x03\x0e\x04\x14\n\x0c\n\x05\x04\x02\x02\
+    \x01\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0e\
+    \x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0e\x12\x13\n\x0b\n\x04\
+    \x04\x02\x02\x02\x12\x03\x0f\x04\x14\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\
+    \x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x0f\x0b\x0f\n\x0c\
+    \n\x05\x04\x02\x02\x02\x03\x12\x03\x0f\x12\x13\n\x0b\n\x04\x04\x02\x02\
+    \x03\x12\x03\x10\x04\x1f\n\x0c\n\x05\x04\x02\x02\x03\x06\x12\x03\x10\x04\
+    \x0e\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\x10\x0f\x1a\n\x0c\n\x05\x04\
+    \x02\x02\x03\x03\x12\x03\x10\x1d\x1e\n\n\n\x02\x04\x03\x12\x04\x12\0\x1b\
+    \x01\n\n\n\x03\x04\x03\x01\x12\x03\x12\x08\x0b\n\x0b\n\x04\x04\x03\x02\0\
+    \x12\x03\x13\x04\x12\n\x0c\n\x05\x04\x03\x02\0\x05\x12\x03\x13\x04\n\n\
+    \x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x13\x0b\r\n\x0c\n\x05\x04\x03\x02\0\
+    \x03\x12\x03\x13\x10\x11\n\x0b\n\x04\x04\x03\x02\x01\x12\x03\x14\x04\x1c\
+    \n\x0c\n\x05\x04\x03\x02\x01\x05\x12\x03\x14\x04\n\n\x0c\n\x05\x04\x03\
+    \x02\x01\x01\x12\x03\x14\x0b\x17\n\x0c\n\x05\x04\x03\x02\x01\x03\x12\x03\
+    \x14\x1a\x1b\n\x0b\n\x04\x04\x03\x02\x02\x12\x03\x15\x04\x14\n\x0c\n\x05\
+    \x04\x03\x02\x02\x05\x12\x03\x15\x04\n\n\x0c\n\x05\x04\x03\x02\x02\x01\
+    \x12\x03\x15\x0b\x0f\n\x0c\n\x05\x04\x03\x02\x02\x03\x12\x03\x15\x12\x13\
+    \n\x0b\n\x04\x04\x03\x02\x03\x12\x03\x16\x04\x14\n\x0c\n\x05\x04\x03\x02\
+    \x03\x05\x12\x03\x16\x04\n\n\x0c\n\x05\x04\x03\x02\x03\x01\x12\x03\x16\
+    \x0b\x0f\n\x0c\n\x05\x04\x03\x02\x03\x03\x12\x03\x16\x12\x13\n\x0b\n\x04\
+    \x04\x03\x02\x04\x12\x03\x17\x04\x20\n\x0c\n\x05\x04\x03\x02\x04\x06\x12\
+    \x03\x17\x04\x10\n\x0c\n\x05\x04\x03\x02\x04\x01\x12\x03\x17\x11\x1b\n\
+    \x0c\n\x05\x04\x03\x02\x04\x03\x12\x03\x17\x1e\x1f\n\x0b\n\x04\x04\x03\
+    \x02\x05\x12\x03\x18\x04\x16\n\x0c\n\x05\x04\x03\x02\x05\x05\x12\x03\x18\
+    \x04\t\n\x0c\n\x05\x04\x03\x02\x05\x01\x12\x03\x18\n\x11\n\x0c\n\x05\x04\
+    \x03\x02\x05\x03\x12\x03\x18\x14\x15\n\x0b\n\x04\x04\x03\x02\x06\x12\x03\
+    \x19\x04\x1c\n\x0c\n\x05\x04\x03\x02\x06\x05\x12\x03\x19\x04\t\n\x0c\n\
+    \x05\x04\x03\x02\x06\x01\x12\x03\x19\n\x17\n\x0c\n\x05\x04\x03\x02\x06\
+    \x03\x12\x03\x19\x1a\x1b\n\x0b\n\x04\x04\x03\x02\x07\x12\x03\x1a\x04\x1a\
+    \n\x0c\n\x05\x04\x03\x02\x07\x05\x12\x03\x1a\x04\t\n\x0c\n\x05\x04\x03\
+    \x02\x07\x01\x12\x03\x1a\n\x15\n\x0c\n\x05\x04\x03\x02\x07\x03\x12\x03\
+    \x1a\x18\x19\n\n\n\x02\x04\x04\x12\x04\x1c\0\x1e\x01\n\n\n\x03\x04\x04\
+    \x01\x12\x03\x1c\x08\x13\n\x0b\n\x04\x04\x04\x02\0\x12\x03\x1d\x04\x1b\n\
+    \x0c\n\x05\x04\x04\x02\0\x04\x12\x03\x1d\x04\x0c\n\x0c\n\x05\x04\x04\x02\
+    \0\x06\x12\x03\x1d\r\x10\n\x0c\n\x05\x04\x04\x02\0\x01\x12\x03\x1d\x11\
+    \x16\n\x0c\n\x05\x04\x04\x02\0\x03\x12\x03\x1d\x19\x1ab\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 143 - 66
rust-lib/flowy-workspace/src/protobuf/model/view_create.rs

@@ -705,6 +705,8 @@ pub struct View {
     pub view_type: ViewType,
     pub version: i64,
     pub belongings: ::protobuf::SingularPtrField<RepeatedView>,
+    pub modified_time: i64,
+    pub create_time: i64,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -887,6 +889,36 @@ impl View {
     pub fn take_belongings(&mut self) -> RepeatedView {
         self.belongings.take().unwrap_or_else(|| RepeatedView::new())
     }
+
+    // int64 modified_time = 8;
+
+
+    pub fn get_modified_time(&self) -> i64 {
+        self.modified_time
+    }
+    pub fn clear_modified_time(&mut self) {
+        self.modified_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_modified_time(&mut self, v: i64) {
+        self.modified_time = v;
+    }
+
+    // int64 create_time = 9;
+
+
+    pub fn get_create_time(&self) -> i64 {
+        self.create_time
+    }
+    pub fn clear_create_time(&mut self) {
+        self.create_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_create_time(&mut self, v: i64) {
+        self.create_time = v;
+    }
 }
 
 impl ::protobuf::Message for View {
@@ -928,6 +960,20 @@ impl ::protobuf::Message for View {
                 7 => {
                     ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.belongings)?;
                 },
+                8 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.modified_time = tmp;
+                },
+                9 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.create_time = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -962,6 +1008,12 @@ impl ::protobuf::Message for View {
             let len = v.compute_size();
             my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         }
+        if self.modified_time != 0 {
+            my_size += ::protobuf::rt::value_size(8, self.modified_time, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if self.create_time != 0 {
+            my_size += ::protobuf::rt::value_size(9, self.create_time, ::protobuf::wire_format::WireTypeVarint);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -991,6 +1043,12 @@ impl ::protobuf::Message for View {
             os.write_raw_varint32(v.get_cached_size())?;
             v.write_to_with_cached_sizes(os)?;
         }
+        if self.modified_time != 0 {
+            os.write_int64(8, self.modified_time)?;
+        }
+        if self.create_time != 0 {
+            os.write_int64(9, self.create_time)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -1064,6 +1122,16 @@ impl ::protobuf::Message for View {
                 |m: &View| { &m.belongings },
                 |m: &mut View| { &mut m.belongings },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "modified_time",
+                |m: &View| { &m.modified_time },
+                |m: &mut View| { &mut m.modified_time },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "create_time",
+                |m: &View| { &m.create_time },
+                |m: &mut View| { &mut m.create_time },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<View>(
                 "View",
                 fields,
@@ -1087,6 +1155,8 @@ impl ::protobuf::Clear for View {
         self.view_type = ViewType::Blank;
         self.version = 0;
         self.belongings.clear();
+        self.modified_time = 0;
+        self.create_time = 0;
         self.unknown_fields.clear();
     }
 }
@@ -1329,76 +1399,83 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \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\"\xd1\x01\n\x04View\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\
+    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\"+\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\xe3\t\n\x06\x12\
-    \x04\0\0\x1f\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\x18\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\n\n\x02\x04\x03\x12\x04\x19\0\x1b\x01\n\n\n\x03\x04\x03\
-    \x01\x12\x03\x19\x08\x14\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x1a\x04\x1c\n\
-    \x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x1a\x04\x0c\n\x0c\n\x05\x04\x03\x02\
-    \0\x06\x12\x03\x1a\r\x11\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x1a\x12\
-    \x17\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x1a\x1a\x1b\n\n\n\x02\x05\0\
-    \x12\x04\x1c\0\x1f\x01\n\n\n\x03\x05\0\x01\x12\x03\x1c\x05\r\n\x0b\n\x04\
-    \x05\0\x02\0\x12\x03\x1d\x04\x0e\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x1d\
-    \x04\t\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x1d\x0c\r\n\x0b\n\x04\x05\0\
-    \x02\x01\x12\x03\x1e\x04\x0c\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x1e\
-    \x04\x07\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x1e\n\x0bb\x06proto3\
+    \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\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 114 - 36
rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs

@@ -432,6 +432,8 @@ pub struct Workspace {
     pub name: ::std::string::String,
     pub desc: ::std::string::String,
     pub apps: ::protobuf::SingularPtrField<super::app_create::RepeatedApp>,
+    pub modified_time: i64,
+    pub create_time: i64,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -558,6 +560,36 @@ impl Workspace {
     pub fn take_apps(&mut self) -> super::app_create::RepeatedApp {
         self.apps.take().unwrap_or_else(|| super::app_create::RepeatedApp::new())
     }
+
+    // int64 modified_time = 5;
+
+
+    pub fn get_modified_time(&self) -> i64 {
+        self.modified_time
+    }
+    pub fn clear_modified_time(&mut self) {
+        self.modified_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_modified_time(&mut self, v: i64) {
+        self.modified_time = v;
+    }
+
+    // int64 create_time = 6;
+
+
+    pub fn get_create_time(&self) -> i64 {
+        self.create_time
+    }
+    pub fn clear_create_time(&mut self) {
+        self.create_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_create_time(&mut self, v: i64) {
+        self.create_time = v;
+    }
 }
 
 impl ::protobuf::Message for Workspace {
@@ -586,6 +618,20 @@ impl ::protobuf::Message for Workspace {
                 4 => {
                     ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.apps)?;
                 },
+                5 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.modified_time = tmp;
+                },
+                6 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.create_time = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -611,6 +657,12 @@ impl ::protobuf::Message for Workspace {
             let len = v.compute_size();
             my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         }
+        if self.modified_time != 0 {
+            my_size += ::protobuf::rt::value_size(5, self.modified_time, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if self.create_time != 0 {
+            my_size += ::protobuf::rt::value_size(6, self.create_time, ::protobuf::wire_format::WireTypeVarint);
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -631,6 +683,12 @@ impl ::protobuf::Message for Workspace {
             os.write_raw_varint32(v.get_cached_size())?;
             v.write_to_with_cached_sizes(os)?;
         }
+        if self.modified_time != 0 {
+            os.write_int64(5, self.modified_time)?;
+        }
+        if self.create_time != 0 {
+            os.write_int64(6, self.create_time)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -689,6 +747,16 @@ impl ::protobuf::Message for Workspace {
                 |m: &Workspace| { &m.apps },
                 |m: &mut Workspace| { &mut m.apps },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "modified_time",
+                |m: &Workspace| { &m.modified_time },
+                |m: &mut Workspace| { &mut m.modified_time },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "create_time",
+                |m: &Workspace| { &m.create_time },
+                |m: &mut Workspace| { &mut m.create_time },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<Workspace>(
                 "Workspace",
                 fields,
@@ -709,6 +777,8 @@ impl ::protobuf::Clear for Workspace {
         self.name.clear();
         self.desc.clear();
         self.apps.clear();
+        self.modified_time = 0;
+        self.create_time = 0;
         self.unknown_fields.clear();
     }
 }
@@ -896,42 +966,50 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     paceRequest\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\x12\x12\n\x04d\
     esc\x18\x02\x20\x01(\tR\x04desc\"?\n\x15CreateWorkspaceParams\x12\x12\n\
     \x04name\x18\x01\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x02\x20\x01(\
-    \tR\x04desc\"e\n\tWorkspace\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
-    \x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\
-    \x20\x01(\tR\x04desc\x12\x20\n\x04apps\x18\x04\x20\x01(\x0b2\x0c.Repeate\
-    dAppR\x04apps\"5\n\x11RepeatedWorkspace\x12\x20\n\x05items\x18\x01\x20\
-    \x03(\x0b2\n.WorkspaceR\x05itemsJ\xfa\x04\n\x06\x12\x04\0\0\x13\x01\n\
-    \x08\n\x01\x0c\x12\x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1a\n\n\n\
-    \x02\x04\0\x12\x04\x03\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\x1e\
-    \n\x0b\n\x04\x04\0\x02\0\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\0\x05\
-    \x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x0f\n\x0c\
-    \n\x05\x04\0\x02\0\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x01\x12\
-    \x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\x0c\n\
-    \x05\x04\0\x02\x01\x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\
-    \x12\x03\x05\x12\x13\n\n\n\x02\x04\x01\x12\x04\x07\0\n\x01\n\n\n\x03\x04\
-    \x01\x01\x12\x03\x07\x08\x1d\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x08\x04\
-    \x14\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\
-    \x02\0\x01\x12\x03\x08\x0b\x0f\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x08\
-    \x12\x13\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\t\x04\x14\n\x0c\n\x05\x04\
-    \x01\x02\x01\x05\x12\x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\
-    \t\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\t\x12\x13\n\n\n\x02\
-    \x04\x02\x12\x04\x0b\0\x10\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0b\x08\x11\
-    \n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0c\x04\x12\n\x0c\n\x05\x04\x02\x02\0\
-    \x05\x12\x03\x0c\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0c\x0b\r\n\
-    \x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0c\x10\x11\n\x0b\n\x04\x04\x02\x02\
-    \x01\x12\x03\r\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\r\x04\n\n\
-    \x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\r\x0b\x0f\n\x0c\n\x05\x04\x02\x02\
-    \x01\x03\x12\x03\r\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x0e\x04\
-    \x14\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\
-    \x02\x02\x02\x01\x12\x03\x0e\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\
-    \x03\x0e\x12\x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x0f\x04\x19\n\x0c\n\
-    \x05\x04\x02\x02\x03\x06\x12\x03\x0f\x04\x0f\n\x0c\n\x05\x04\x02\x02\x03\
-    \x01\x12\x03\x0f\x10\x14\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x0f\x17\
-    \x18\n\n\n\x02\x04\x03\x12\x04\x11\0\x13\x01\n\n\n\x03\x04\x03\x01\x12\
-    \x03\x11\x08\x19\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x12\x04!\n\x0c\n\x05\
-    \x04\x03\x02\0\x04\x12\x03\x12\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\x06\x12\
-    \x03\x12\r\x16\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x12\x17\x1c\n\x0c\n\
-    \x05\x04\x03\x02\0\x03\x12\x03\x12\x1f\x20b\x06proto3\
+    \tR\x04desc\"\xab\x01\n\tWorkspace\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
+    \x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\
+    \x18\x03\x20\x01(\tR\x04desc\x12\x20\n\x04apps\x18\x04\x20\x01(\x0b2\x0c\
+    .RepeatedAppR\x04apps\x12#\n\rmodified_time\x18\x05\x20\x01(\x03R\x0cmod\
+    ifiedTime\x12\x1f\n\x0bcreate_time\x18\x06\x20\x01(\x03R\ncreateTime\"5\
+    \n\x11RepeatedWorkspace\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.Works\
+    paceR\x05itemsJ\xe8\x05\n\x06\x12\x04\0\0\x15\x01\n\x08\n\x01\x0c\x12\
+    \x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1a\n\n\n\x02\x04\0\x12\x04\
+    \x03\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\x1e\n\x0b\n\x04\x04\0\
+    \x02\0\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x04\x04\n\
+    \n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\0\
+    \x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x05\x04\x14\n\
+    \x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x01\
+    \x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x05\x12\
+    \x13\n\n\n\x02\x04\x01\x12\x04\x07\0\n\x01\n\n\n\x03\x04\x01\x01\x12\x03\
+    \x07\x08\x1d\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x08\x04\x14\n\x0c\n\x05\
+    \x04\x01\x02\0\x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\
+    \x03\x08\x0b\x0f\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x08\x12\x13\n\x0b\
+    \n\x04\x04\x01\x02\x01\x12\x03\t\x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\
+    \x12\x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\t\x0b\x0f\n\x0c\
+    \n\x05\x04\x01\x02\x01\x03\x12\x03\t\x12\x13\n\n\n\x02\x04\x02\x12\x04\
+    \x0b\0\x12\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0b\x08\x11\n\x0b\n\x04\x04\
+    \x02\x02\0\x12\x03\x0c\x04\x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0c\
+    \x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0c\x0b\r\n\x0c\n\x05\x04\
+    \x02\x02\0\x03\x12\x03\x0c\x10\x11\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\r\
+    \x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\
+    \x02\x02\x01\x01\x12\x03\r\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\
+    \x03\r\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x0e\x04\x14\n\x0c\n\
+    \x05\x04\x02\x02\x02\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\x02\x02\x02\
+    \x01\x12\x03\x0e\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\x0e\x12\
+    \x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x0f\x04\x19\n\x0c\n\x05\x04\x02\
+    \x02\x03\x06\x12\x03\x0f\x04\x0f\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\
+    \x0f\x10\x14\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x0f\x17\x18\n\x0b\n\
+    \x04\x04\x02\x02\x04\x12\x03\x10\x04\x1c\n\x0c\n\x05\x04\x02\x02\x04\x05\
+    \x12\x03\x10\x04\t\n\x0c\n\x05\x04\x02\x02\x04\x01\x12\x03\x10\n\x17\n\
+    \x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x10\x1a\x1b\n\x0b\n\x04\x04\x02\
+    \x02\x05\x12\x03\x11\x04\x1a\n\x0c\n\x05\x04\x02\x02\x05\x05\x12\x03\x11\
+    \x04\t\n\x0c\n\x05\x04\x02\x02\x05\x01\x12\x03\x11\n\x15\n\x0c\n\x05\x04\
+    \x02\x02\x05\x03\x12\x03\x11\x18\x19\n\n\n\x02\x04\x03\x12\x04\x13\0\x15\
+    \x01\n\n\n\x03\x04\x03\x01\x12\x03\x13\x08\x19\n\x0b\n\x04\x04\x03\x02\0\
+    \x12\x03\x14\x04!\n\x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x14\x04\x0c\n\
+    \x0c\n\x05\x04\x03\x02\0\x06\x12\x03\x14\r\x16\n\x0c\n\x05\x04\x03\x02\0\
+    \x01\x12\x03\x14\x17\x1c\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x14\x1f\
+    \x20b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 0
rust-lib/flowy-workspace/src/protobuf/proto/app_create.proto

@@ -23,6 +23,8 @@ message App {
     string desc = 4;
     RepeatedView belongings = 5;
     int64 version = 6;
+    int64 modified_time = 7;
+    int64 create_time = 8;
 }
 message RepeatedApp {
     repeated App items = 1;

+ 2 - 0
rust-lib/flowy-workspace/src/protobuf/proto/view_create.proto

@@ -22,6 +22,8 @@ message View {
     ViewType view_type = 5;
     int64 version = 6;
     RepeatedView belongings = 7;
+    int64 modified_time = 8;
+    int64 create_time = 9;
 }
 message RepeatedView {
     repeated View items = 1;

+ 2 - 0
rust-lib/flowy-workspace/src/protobuf/proto/workspace_create.proto

@@ -14,6 +14,8 @@ message Workspace {
     string name = 2;
     string desc = 3;
     RepeatedApp apps = 4;
+    int64 modified_time = 5;
+    int64 create_time = 6;
 }
 message RepeatedWorkspace {
     repeated Workspace items = 1;

+ 64 - 20
rust-lib/flowy-workspace/src/services/app_controller.rs

@@ -3,10 +3,9 @@ use crate::{
     errors::*,
     module::{WorkspaceDatabase, WorkspaceUser},
     observable::*,
-    services::{server::Server, ViewController},
+    services::{helper::spawn, server::Server, ViewController},
     sql_tables::app::{AppTable, AppTableChangeset, AppTableSql},
 };
-use flowy_dispatch::prelude::DispatchFuture;
 
 use std::sync::Arc;
 
@@ -34,44 +33,89 @@ impl AppController {
         }
     }
 
-    pub(crate) fn create_app(&self, params: CreateAppParams) -> Result<App, WorkspaceError> {
-        // TODO: server
-        let app_table = AppTable::new(params);
-        let app: App = app_table.clone().into();
+    pub(crate) async fn create_app(&self, params: CreateAppParams) -> Result<App, WorkspaceError> {
+        let app = self.create_app_on_server(params).await?;
+        let app_table = AppTable::new(app.clone());
         let _ = self.sql.create_app(app_table)?;
-
         send_observable(&app.workspace_id, WorkspaceObservable::WorkspaceCreateApp);
         Ok(app)
     }
 
-    pub(crate) async fn read_app(&self, app_id: &str, is_trash: bool) -> Result<App, WorkspaceError> {
-        let app_table = self.async_read_app(&app_id, is_trash).await?;
+    pub(crate) async fn read_app(&self, params: QueryAppParams) -> Result<App, WorkspaceError> {
+        let app_table = self.sql.read_app(&params.app_id, params.is_trash)?;
+        let _ = self.read_app_on_server(params).await?;
         Ok(app_table.into())
     }
 
     pub(crate) async fn delete_app(&self, app_id: &str) -> Result<(), WorkspaceError> {
         let app = self.sql.delete_app(app_id)?;
+        let _ = self.delete_app_on_server(app_id).await?;
         send_observable(&app.workspace_id, WorkspaceObservable::WorkspaceDeleteApp);
         Ok(())
     }
 
     pub(crate) async fn update_app(&self, params: UpdateAppParams) -> Result<(), WorkspaceError> {
-        let changeset = AppTableChangeset::new(params);
+        let changeset = AppTableChangeset::new(params.clone());
         let app_id = changeset.id.clone();
         let _ = self.sql.update_app(changeset)?;
+        let _ = self.update_app_on_server(params).await?;
         send_observable(&app_id, WorkspaceObservable::AppUpdated);
         Ok(())
     }
+}
 
-    fn async_read_app(&self, app_id: &str, is_trash: bool) -> DispatchFuture<Result<AppTable, WorkspaceError>> {
-        let sql = self.sql.clone();
-        let app_id = app_id.to_owned();
-        DispatchFuture {
-            fut: Box::pin(async move {
-                let app_table = sql.read_app(&app_id, is_trash)?;
-                // TODO: fetch app from remote server
-                Ok(app_table)
-            }),
-        }
+impl AppController {
+    async fn create_app_on_server(&self, params: CreateAppParams) -> Result<App, WorkspaceError> {
+        let token = self.user.token()?;
+        let app = self.server.create_app(&token, params).await?;
+        Ok(app)
+    }
+
+    async fn update_app_on_server(&self, params: UpdateAppParams) -> Result<(), WorkspaceError> {
+        let token = self.user.token()?;
+        let server = self.server.clone();
+        spawn(async move {
+            match server.update_app(&token, params).await {
+                Ok(_) => {},
+                Err(e) => {
+                    // TODO: retry?
+                    log::error!("Update app failed: {:?}", e);
+                },
+            }
+        });
+        Ok(())
+    }
+
+    async fn delete_app_on_server(&self, app_id: &str) -> Result<(), WorkspaceError> {
+        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);
+                },
+            }
+        });
+        Ok(())
+    }
+
+    async fn read_app_on_server(&self, params: QueryAppParams) -> Result<(), WorkspaceError> {
+        let token = self.user.token()?;
+        let server = self.server.clone();
+        spawn(async move {
+            match server.read_app(&token, params).await {
+                Ok(_) => {},
+                Err(e) => {
+                    // TODO: retry?
+                    log::error!("Read app failed: {:?}", e);
+                },
+            }
+        });
+        Ok(())
     }
 }

+ 10 - 1
rust-lib/flowy-workspace/src/services/server/server_api_mock.rs

@@ -14,17 +14,20 @@ use crate::{
     errors::WorkspaceError,
     services::server::WorkspaceServerAPI,
 };
-use flowy_infra::{future::ResultFuture, uuid};
+use flowy_infra::{future::ResultFuture, timestamp, uuid};
 
 pub struct WorkspaceServerMock {}
 
 impl WorkspaceServerAPI for WorkspaceServerMock {
     fn create_workspace(&self, _token: &str, params: CreateWorkspaceParams) -> ResultFuture<Workspace, WorkspaceError> {
+        let time = timestamp();
         let workspace = Workspace {
             id: uuid(),
             name: params.name,
             desc: params.desc,
             apps: RepeatedApp::default(),
+            modified_time: time,
+            create_time: time,
         };
 
         ResultFuture::new(async { Ok(workspace) })
@@ -46,6 +49,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
     }
 
     fn create_view(&self, _token: &str, params: CreateViewParams) -> ResultFuture<View, WorkspaceError> {
+        let time = timestamp();
         let view = View {
             id: uuid(),
             belong_to_id: params.belong_to_id,
@@ -54,6 +58,8 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
             view_type: params.view_type,
             version: 0,
             belongings: RepeatedView::default(),
+            modified_time: time,
+            create_time: time,
         };
         ResultFuture::new(async { Ok(view) })
     }
@@ -71,6 +77,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
     }
 
     fn create_app(&self, _token: &str, params: CreateAppParams) -> ResultFuture<App, WorkspaceError> {
+        let time = timestamp();
         let app = App {
             id: uuid(),
             workspace_id: params.workspace_id,
@@ -78,6 +85,8 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
             desc: params.desc,
             belongings: RepeatedView::default(),
             version: 0,
+            modified_time: time,
+            create_time: time,
         };
         ResultFuture::new(async { Ok(app) })
     }

+ 73 - 8
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -3,44 +3,52 @@ use crate::{
     errors::WorkspaceError,
     module::WorkspaceDatabase,
     observable::{send_observable, WorkspaceObservable},
-    services::server::Server,
+    services::{helper::spawn, server::Server},
     sql_tables::view::{ViewTable, ViewTableChangeset, ViewTableSql},
 };
 
+use crate::{
+    entities::view::{DeleteViewParams, QueryViewParams},
+    module::WorkspaceUser,
+};
 use std::sync::Arc;
 
 pub(crate) struct ViewController {
+    user: Arc<dyn WorkspaceUser>,
     sql: Arc<ViewTableSql>,
     server: Server,
 }
 
 impl ViewController {
-    pub(crate) fn new(database: Arc<dyn WorkspaceDatabase>, server: Server) -> Self {
+    pub(crate) fn new(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>, server: Server) -> Self {
         let sql = Arc::new(ViewTableSql { database });
-        Self { sql, server }
+        Self { user, sql, server }
     }
 
     pub(crate) async fn create_view(&self, params: CreateViewParams) -> Result<View, WorkspaceError> {
-        let view_table = ViewTable::new(params);
-        let view: View = view_table.clone().into();
+        let view = self.create_view_on_server(params).await?;
+        let view_table = ViewTable::new(view.clone());
         let _ = self.sql.create_view(view_table)?;
 
         send_observable(&view.belong_to_id, WorkspaceObservable::AppCreateView);
         Ok(view)
     }
 
-    pub(crate) async fn read_view(&self, view_id: &str, is_trash: bool) -> Result<View, WorkspaceError> {
-        let view_table = self.sql.read_view(view_id, is_trash)?;
+    pub(crate) async fn read_view(&self, params: QueryViewParams) -> Result<View, WorkspaceError> {
+        let view_table = self.sql.read_view(&params.view_id, params.is_trash)?;
         let view: View = view_table.into();
+        let _ = self.read_view_on_server(params).await?;
         Ok(view)
     }
 
     pub(crate) async fn delete_view(&self, view_id: &str) -> Result<(), WorkspaceError> {
         let view = self.sql.delete_view(view_id)?;
+        let _ = self.delete_view_on_server(view_id).await?;
         send_observable(&view.belong_to_id, WorkspaceObservable::AppDeleteView);
         Ok(())
     }
 
+    // belong_to_id will be the app_id or view_id.
     pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<Vec<View>, WorkspaceError> {
         let views = self
             .sql
@@ -53,11 +61,68 @@ impl ViewController {
     }
 
     pub(crate) async fn update_view(&self, params: UpdateViewParams) -> Result<(), WorkspaceError> {
-        let changeset = ViewTableChangeset::new(params);
+        let changeset = ViewTableChangeset::new(params.clone());
         let view_id = changeset.id.clone();
         let _ = self.sql.update_view(changeset)?;
+
+        let _ = self.update_view_on_server(params).await?;
         send_observable(&view_id, WorkspaceObservable::ViewUpdated);
+        Ok(())
+    }
+}
+
+impl ViewController {
+    async fn create_view_on_server(&self, params: CreateViewParams) -> Result<View, WorkspaceError> {
+        let token = self.user.token()?;
+        let view = self.server.create_view(&token, params).await?;
+        Ok(view)
+    }
+
+    async fn update_view_on_server(&self, params: UpdateViewParams) -> Result<(), WorkspaceError> {
+        let token = self.user.token()?;
+        let server = self.server.clone();
+        spawn(async move {
+            match server.update_view(&token, params).await {
+                Ok(_) => {},
+                Err(e) => {
+                    // TODO: retry?
+                    log::error!("Update view failed: {:?}", e);
+                },
+            }
+        });
+        Ok(())
+    }
+
+    async fn delete_view_on_server(&self, view_id: &str) -> Result<(), WorkspaceError> {
+        let token = self.user.token()?;
+        let server = self.server.clone();
+        let params = DeleteViewParams {
+            view_id: view_id.to_string(),
+        };
+        spawn(async move {
+            match server.delete_view(&token, params).await {
+                Ok(_) => {},
+                Err(e) => {
+                    // TODO: retry?
+                    log::error!("Delete view failed: {:?}", e);
+                },
+            }
+        });
+        Ok(())
+    }
 
+    async fn read_view_on_server(&self, params: QueryViewParams) -> Result<(), WorkspaceError> {
+        let token = self.user.token()?;
+        let server = self.server.clone();
+        spawn(async move {
+            match server.read_view(&token, params).await {
+                Ok(_) => {},
+                Err(e) => {
+                    // TODO: retry?
+                    log::error!("Read view failed: {:?}", e);
+                },
+            }
+        });
         Ok(())
     }
 }

+ 23 - 21
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -35,30 +35,27 @@ impl WorkspaceController {
     }
 
     pub(crate) async fn create_workspace(&self, params: CreateWorkspaceParams) -> Result<Workspace, WorkspaceError> {
-        let _ = self.create_workspace_on_server(params.clone()).await?;
-
+        let workspace = self.create_workspace_on_server(params.clone()).await?;
         let user_id = self.user.user_id()?;
-        let workspace_table = WorkspaceTable::new(params, &user_id);
-        let workspace: Workspace = workspace_table.clone().into();
+        let workspace_table = WorkspaceTable::new(workspace.clone(), &user_id);
         let _ = self.sql.create_workspace(workspace_table)?;
         send_observable(&user_id, WorkspaceObservable::UserCreateWorkspace);
         Ok(workspace)
     }
 
-    pub(crate) fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
+    pub(crate) async fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
         let changeset = WorkspaceTableChangeset::new(params.clone());
         let workspace_id = changeset.id.clone();
         let _ = self.sql.update_workspace(changeset)?;
-
-        let _ = self.update_workspace_on_server(params.clone())?;
+        let _ = self.update_workspace_on_server(params).await?;
         send_observable(&workspace_id, WorkspaceObservable::WorkspaceUpdated);
         Ok(())
     }
 
-    pub(crate) fn delete_workspace(&self, workspace_id: &str) -> Result<(), WorkspaceError> {
+    pub(crate) async fn delete_workspace(&self, workspace_id: &str) -> Result<(), WorkspaceError> {
         let user_id = self.user.user_id()?;
         let _ = self.sql.delete_workspace(workspace_id)?;
-
+        let _ = self.delete_workspace_on_server(workspace_id).await?;
         send_observable(&user_id, WorkspaceObservable::UserDeleteWorkspace);
         Ok(())
     }
@@ -80,7 +77,7 @@ impl WorkspaceController {
     pub(crate) async fn read_workspaces(&self, params: QueryWorkspaceParams) -> Result<RepeatedWorkspace, WorkspaceError> {
         self.read_workspaces_on_server(params.clone());
         let user_id = self.user.user_id()?;
-        let workspace_tables = self.read_workspace_table(params.workspace_id, user_id).await?;
+        let workspace_tables = self.read_workspace_table(params.workspace_id.clone(), user_id).await?;
         let mut workspaces = vec![];
         for table in workspace_tables {
             let apps = self.read_apps(&table.id).await?;
@@ -88,6 +85,8 @@ impl WorkspaceController {
             workspace.apps.items = apps;
             workspaces.push(workspace);
         }
+
+        let _ = self.read_workspaces_on_server(params).await?;
         Ok(RepeatedWorkspace { items: workspaces })
     }
 
@@ -141,20 +140,20 @@ impl WorkspaceController {
 }
 
 impl WorkspaceController {
-    fn token_server(&self) -> Result<(String, Server), WorkspaceError> {
+    fn token_with_server(&self) -> Result<(String, Server), WorkspaceError> {
         let token = self.user.token()?;
         let server = self.server.clone();
         Ok((token, server))
     }
 
-    async fn create_workspace_on_server(&self, params: CreateWorkspaceParams) -> Result<(), WorkspaceError> {
+    async fn create_workspace_on_server(&self, params: CreateWorkspaceParams) -> Result<Workspace, WorkspaceError> {
         let token = self.user.token()?;
-        let _ = self.server.create_workspace(&token, params).await?;
-        Ok(())
+        let workspace = self.server.create_workspace(&token, params).await?;
+        Ok(workspace)
     }
 
-    fn update_workspace_on_server(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
-        let (token, server) = self.token_server()?;
+    async fn update_workspace_on_server(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
+        let (token, server) = self.token_with_server()?;
         spawn(async move {
             match server.update_workspace(&token, params).await {
                 Ok(_) => {},
@@ -167,8 +166,11 @@ impl WorkspaceController {
         Ok(())
     }
 
-    fn delete_workspace_on_server(&self, params: DeleteWorkspaceParams) -> Result<(), WorkspaceError> {
-        let (token, server) = self.token_server()?;
+    async fn delete_workspace_on_server(&self, workspace_id: &str) -> Result<(), WorkspaceError> {
+        let params = DeleteWorkspaceParams {
+            workspace_id: workspace_id.to_string(),
+        };
+        let (token, server) = self.token_with_server()?;
         spawn(async move {
             match server.delete_workspace(&token, params).await {
                 Ok(_) => {},
@@ -181,11 +183,11 @@ impl WorkspaceController {
         Ok(())
     }
 
-    fn read_workspaces_on_server(&self, params: QueryWorkspaceParams) -> Result<(), WorkspaceError> {
-        let (token, server) = self.token_server()?;
+    async fn read_workspaces_on_server(&self, params: QueryWorkspaceParams) -> Result<(), WorkspaceError> {
+        let (token, server) = self.token_with_server()?;
         spawn(async move {
             match server.read_workspace(&token, params).await {
-                Ok(_) => {
+                Ok(_workspaces) => {
                     // TODO: notify
                 },
                 Err(e) => {

+ 14 - 18
rust-lib/flowy-workspace/src/sql_tables/app/app_table.rs

@@ -1,6 +1,6 @@
 use crate::{
     entities::{
-        app::{App, ColorStyle, CreateAppParams, UpdateAppParams},
+        app::{App, ColorStyle, UpdateAppParams},
         view::RepeatedView,
     },
     impl_sql_binary_expression,
@@ -8,7 +8,7 @@ use crate::{
 };
 use diesel::sql_types::Binary;
 use flowy_database::schema::app_table;
-use flowy_infra::{timestamp, uuid};
+
 use serde::{Deserialize, Serialize, __private::TryFrom};
 use std::convert::TryInto;
 
@@ -29,18 +29,16 @@ pub(crate) struct AppTable {
 }
 
 impl AppTable {
-    pub fn new(params: CreateAppParams) -> Self {
-        let app_id = uuid();
-        let time = timestamp();
+    pub fn new(app: App) -> Self {
         Self {
-            id: app_id,
-            workspace_id: params.workspace_id,
-            name: params.name,
-            desc: params.desc,
-            color_style: params.color_style.into(),
+            id: app.id,
+            workspace_id: app.workspace_id,
+            name: app.name,
+            desc: app.desc,
+            color_style: ColorStyleCol::default(),
             last_view_id: None,
-            modified_time: time,
-            create_time: time,
+            modified_time: app.modified_time,
+            create_time: app.create_time,
             version: 0,
             is_trash: false,
         }
@@ -64,17 +62,13 @@ impl std::convert::From<ColorStyle> for ColorStyleCol {
 impl std::convert::TryInto<Vec<u8>> for &ColorStyleCol {
     type Error = String;
 
-    fn try_into(self) -> Result<Vec<u8>, Self::Error> {
-        bincode::serialize(self).map_err(|e| format!("{:?}", e))
-    }
+    fn try_into(self) -> Result<Vec<u8>, Self::Error> { bincode::serialize(self).map_err(|e| format!("{:?}", e)) }
 }
 
 impl std::convert::TryFrom<&[u8]> for ColorStyleCol {
     type Error = String;
 
-    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
-        bincode::deserialize(value).map_err(|e| format!("{:?}", e))
-    }
+    fn try_from(value: &[u8]) -> Result<Self, Self::Error> { bincode::deserialize(value).map_err(|e| format!("{:?}", e)) }
 }
 
 impl_sql_binary_expression!(ColorStyleCol);
@@ -108,6 +102,8 @@ impl std::convert::Into<App> for AppTable {
             desc: self.desc,
             belongings: RepeatedView::default(),
             version: self.version,
+            modified_time: self.modified_time,
+            create_time: self.create_time,
         }
     }
 }

+ 14 - 13
rust-lib/flowy-workspace/src/sql_tables/view/view_table.rs

@@ -1,11 +1,11 @@
 use crate::{
-    entities::view::{CreateViewParams, RepeatedView, UpdateViewParams, View, ViewType},
+    entities::view::{RepeatedView, UpdateViewParams, View, ViewType},
     impl_sql_integer_expression,
     sql_tables::app::AppTable,
 };
 use diesel::sql_types::Integer;
 use flowy_database::schema::view_table;
-use flowy_infra::{timestamp, uuid};
+use flowy_infra::timestamp;
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
 #[belongs_to(AppTable, foreign_key = "belong_to_id")]
@@ -24,22 +24,21 @@ pub(crate) struct ViewTable {
 }
 
 impl ViewTable {
-    pub fn new(params: CreateViewParams) -> Self {
-        let view_id = uuid();
-        let time = timestamp();
-        let view_type = match params.view_type {
+    pub fn new(view: View) -> Self {
+        let view_type = match view.view_type {
             ViewType::Blank => ViewTableType::Docs,
             ViewType::Doc => ViewTableType::Docs,
         };
 
         ViewTable {
-            id: view_id,
-            belong_to_id: params.belong_to_id,
-            name: params.name,
-            desc: params.desc,
-            modified_time: time,
-            create_time: time,
-            thumbnail: params.thumbnail,
+            id: view.id,
+            belong_to_id: view.belong_to_id,
+            name: view.name,
+            desc: view.desc,
+            modified_time: view.modified_time,
+            create_time: view.create_time,
+            // TODO: thumbnail
+            thumbnail: "".to_owned(),
             view_type,
             version: 0,
             is_trash: false,
@@ -60,7 +59,9 @@ impl std::convert::Into<View> for ViewTable {
             desc: self.desc,
             view_type,
             belongings: RepeatedView::default(),
+            modified_time: self.modified_time,
             version: self.version,
+            create_time: self.create_time,
         }
     }
 }

+ 20 - 30
rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_table.rs

@@ -1,9 +1,8 @@
 use crate::entities::{
     app::RepeatedApp,
-    workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, Workspace},
+    workspace::{UpdateWorkspaceParams, Workspace},
 };
 use flowy_database::schema::workspace_table;
-use flowy_infra::{timestamp, uuid};
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable)]
 #[table_name = "workspace_table"]
@@ -19,26 +18,28 @@ pub struct WorkspaceTable {
 
 impl WorkspaceTable {
     #[allow(dead_code)]
-    pub fn new(params: CreateWorkspaceParams, user_id: &str) -> Self {
-        let mut workspace = WorkspaceTable::default();
-        workspace.name = params.name;
-        workspace.desc = params.desc;
-        workspace.user_id = user_id.to_string();
-        workspace
+    pub fn new(workspace: Workspace, user_id: &str) -> Self {
+        WorkspaceTable {
+            id: workspace.id,
+            name: workspace.name,
+            desc: workspace.desc,
+            modified_time: workspace.modified_time,
+            create_time: workspace.create_time,
+            user_id: user_id.to_owned(),
+            version: 0,
+        }
     }
 }
 
-impl std::default::Default for WorkspaceTable {
-    fn default() -> Self {
-        let time = timestamp();
-        WorkspaceTable {
-            id: uuid(),
-            name: String::default(),
-            desc: String::default(),
-            modified_time: time,
-            create_time: time,
-            user_id: String::default(),
-            version: 0,
+impl std::convert::Into<Workspace> for WorkspaceTable {
+    fn into(self) -> Workspace {
+        Workspace {
+            id: self.id,
+            name: self.name,
+            desc: self.desc,
+            apps: RepeatedApp::default(),
+            modified_time: self.modified_time,
+            create_time: self.create_time,
         }
     }
 }
@@ -60,14 +61,3 @@ impl WorkspaceTableChangeset {
         }
     }
 }
-
-impl std::convert::Into<Workspace> for WorkspaceTable {
-    fn into(self) -> Workspace {
-        Workspace {
-            id: self.id,
-            name: self.name,
-            desc: self.desc,
-            apps: RepeatedApp::default(),
-        }
-    }
-}