Преглед на файлове

create app in workspace

appflowy преди 3 години
родител
ревизия
b1f4f0520a
променени са 61 файла, в които са добавени 1373 реда и са изтрити 636 реда
  1. 41 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pb.dart
  2. 10 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pbjson.dart
  3. 4 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  4. 3 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  5. 1 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/protobuf.dart
  6. 18 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart
  7. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart
  8. 72 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pb.dart
  9. 7 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbenum.dart
  10. 21 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbjson.dart
  11. 9 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbserver.dart
  12. 13 78
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pb.dart
  13. 5 16
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pbjson.dart
  14. 1 1
      rust-lib/flowy-database/src/lib.rs
  15. 3 2
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  16. 1 1
      rust-lib/flowy-derive/src/proto_buf/serialize.rs
  17. 10 4
      rust-lib/flowy-sdk/src/deps_resolve/workspace_user_impl.rs
  18. 6 3
      rust-lib/flowy-sdk/src/module.rs
  19. 1 1
      rust-lib/flowy-test/src/builder.rs
  20. 1 1
      rust-lib/flowy-test/src/tester.rs
  21. 1 1
      rust-lib/flowy-user/src/errors.rs
  22. 1 1
      rust-lib/flowy-user/tests/event/user_status_test.rs
  23. 2 1
      rust-lib/flowy-workspace/Cargo.toml
  24. 10 1
      rust-lib/flowy-workspace/src/entities/app/app_create.rs
  25. 0 2
      rust-lib/flowy-workspace/src/entities/view/parser/view_thumbnail.rs
  26. 0 1
      rust-lib/flowy-workspace/src/entities/view/parser/view_type.rs
  27. 10 1
      rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs
  28. 35 50
      rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs
  29. 1 11
      rust-lib/flowy-workspace/src/entities/workspace/workspace_user_detail.rs
  30. 10 6
      rust-lib/flowy-workspace/src/event.rs
  31. 20 11
      rust-lib/flowy-workspace/src/handlers/workspace_handler.rs
  32. 43 0
      rust-lib/flowy-workspace/src/macros.rs
  33. 23 7
      rust-lib/flowy-workspace/src/module.rs
  34. 202 30
      rust-lib/flowy-workspace/src/protobuf/model/app_create.rs
  35. 22 16
      rust-lib/flowy-workspace/src/protobuf/model/event.rs
  36. 3 0
      rust-lib/flowy-workspace/src/protobuf/model/mod.rs
  37. 83 22
      rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs
  38. 243 0
      rust-lib/flowy-workspace/src/protobuf/model/workspace_query.rs
  39. 31 257
      rust-lib/flowy-workspace/src/protobuf/model/workspace_user_detail.rs
  40. 3 0
      rust-lib/flowy-workspace/src/protobuf/proto/app_create.proto
  41. 2 1
      rust-lib/flowy-workspace/src/protobuf/proto/event.proto
  42. 2 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_create.proto
  43. 6 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_query.proto
  44. 1 6
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_user_detail.proto
  45. 49 12
      rust-lib/flowy-workspace/src/services/app_controller.rs
  46. 8 10
      rust-lib/flowy-workspace/src/services/view_controller.rs
  47. 64 34
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  48. 57 0
      rust-lib/flowy-workspace/src/sql_tables/app/app_sql.rs
  49. 0 0
      rust-lib/flowy-workspace/src/sql_tables/app/app_table.rs
  50. 4 2
      rust-lib/flowy-workspace/src/sql_tables/app/mod.rs
  51. 4 2
      rust-lib/flowy-workspace/src/sql_tables/view/mod.rs
  52. 23 0
      rust-lib/flowy-workspace/src/sql_tables/view/view_sql.rs
  53. 1 1
      rust-lib/flowy-workspace/src/sql_tables/view/view_table.rs
  54. 4 2
      rust-lib/flowy-workspace/src/sql_tables/workspace/mod.rs
  55. 67 0
      rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_sql.rs
  56. 5 1
      rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_table.rs
  57. 28 5
      rust-lib/flowy-workspace/tests/event/app_test.rs
  58. 45 0
      rust-lib/flowy-workspace/tests/event/helper.rs
  59. 1 0
      rust-lib/flowy-workspace/tests/event/main.rs
  60. 0 0
      rust-lib/flowy-workspace/tests/event/view_test.rs
  61. 30 30
      rust-lib/flowy-workspace/tests/event/workspace_test.rs

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

@@ -236,3 +236,44 @@ class App extends $pb.GeneratedMessage {
   void clearDesc() => clearField(4);
 }
 
+class RepeatedApp extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedApp', createEmptyInstance: create)
+    ..pc<App>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: App.create)
+    ..hasRequiredFields = false
+  ;
+
+  RepeatedApp._() : super();
+  factory RepeatedApp({
+    $core.Iterable<App>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory RepeatedApp.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedApp.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  RepeatedApp clone() => RepeatedApp()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RepeatedApp copyWith(void Function(RepeatedApp) updates) => super.copyWith((message) => updates(message as RepeatedApp)) as RepeatedApp; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RepeatedApp create() => RepeatedApp._();
+  RepeatedApp createEmptyInstance() => create();
+  static $pb.PbList<RepeatedApp> createRepeated() => $pb.PbList<RepeatedApp>();
+  @$core.pragma('dart2js:noInline')
+  static RepeatedApp getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedApp>(create);
+  static RepeatedApp? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<App> get items => $_getList(0);
+}
+

+ 10 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pbjson.dart

@@ -44,3 +44,13 @@ const App$json = const {
 
 /// Descriptor for `App`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List appDescriptor = $convert.base64Decode('CgNBcHASDgoCaWQYASABKAlSAmlkEiEKDHdvcmtzcGFjZV9pZBgCIAEoCVILd29ya3NwYWNlSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNj');
+@$core.Deprecated('Use repeatedAppDescriptor instead')
+const RepeatedApp$json = const {
+  '1': 'RepeatedApp',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.App', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `RepeatedApp`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedAppDescriptor = $convert.base64Decode('CgtSZXBlYXRlZEFwcBIaCgVpdGVtcxgBIAMoCzIELkFwcFIFaXRlbXM=');

+ 4 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart

@@ -11,13 +11,15 @@ import 'package:protobuf/protobuf.dart' as $pb;
 
 class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent CreateWorkspace = WorkspaceEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateWorkspace');
-  static const WorkspaceEvent GetWorkspaceDetail = WorkspaceEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspaceDetail');
+  static const WorkspaceEvent GetCurWorkspace = WorkspaceEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetCurWorkspace');
+  static const WorkspaceEvent GetWorkspace = WorkspaceEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspace');
   static const WorkspaceEvent CreateApp = WorkspaceEvent._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateApp');
   static const WorkspaceEvent CreateView = WorkspaceEvent._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateView');
 
   static const $core.List<WorkspaceEvent> values = <WorkspaceEvent> [
     CreateWorkspace,
-    GetWorkspaceDetail,
+    GetCurWorkspace,
+    GetWorkspace,
     CreateApp,
     CreateView,
   ];

+ 3 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart

@@ -13,11 +13,12 @@ const WorkspaceEvent$json = const {
   '1': 'WorkspaceEvent',
   '2': const [
     const {'1': 'CreateWorkspace', '2': 0},
-    const {'1': 'GetWorkspaceDetail', '2': 1},
+    const {'1': 'GetCurWorkspace', '2': 1},
+    const {'1': 'GetWorkspace', '2': 2},
     const {'1': 'CreateApp', '2': 101},
     const {'1': 'CreateView', '2': 201},
   ],
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIWChJHZXRXb3Jrc3BhY2VEZXRhaWwQARINCglDcmVhdGVBcHAQZRIPCgpDcmVhdGVWaWV3EMkB');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABITCg9HZXRDdXJXb3Jrc3BhY2UQARIQCgxHZXRXb3Jrc3BhY2UQAhINCglDcmVhdGVBcHAQZRIPCgpDcmVhdGVWaWV3EMkB');

+ 1 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/protobuf.dart

@@ -2,6 +2,7 @@
 export './errors.pb.dart';
 export './workspace_update.pb.dart';
 export './app_create.pb.dart';
+export './workspace_query.pb.dart';
 export './event.pb.dart';
 export './view_create.pb.dart';
 export './workspace_user_detail.pb.dart';

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

@@ -9,6 +9,8 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
+import 'app_create.pb.dart' as $0;
+
 class CreateWorkspaceRequest extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateWorkspaceRequest', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
@@ -75,6 +77,7 @@ class Workspace extends $pb.GeneratedMessage {
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..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)
     ..hasRequiredFields = false
   ;
 
@@ -83,6 +86,7 @@ class Workspace extends $pb.GeneratedMessage {
     $core.String? id,
     $core.String? name,
     $core.String? desc,
+    $0.RepeatedApp? apps,
   }) {
     final _result = create();
     if (id != null) {
@@ -94,6 +98,9 @@ class Workspace extends $pb.GeneratedMessage {
     if (desc != null) {
       _result.desc = desc;
     }
+    if (apps != null) {
+      _result.apps = apps;
+    }
     return _result;
   }
   factory Workspace.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -143,5 +150,16 @@ class Workspace extends $pb.GeneratedMessage {
   $core.bool hasDesc() => $_has(2);
   @$pb.TagNumber(3)
   void clearDesc() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $0.RepeatedApp get apps => $_getN(3);
+  @$pb.TagNumber(4)
+  set apps($0.RepeatedApp v) { setField(4, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasApps() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearApps() => clearField(4);
+  @$pb.TagNumber(4)
+  $0.RepeatedApp ensureApps() => $_ensure(3);
 }
 

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

@@ -26,8 +26,9 @@ const Workspace$json = const {
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
     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'},
   ],
 };
 
 /// Descriptor for `Workspace`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYw==');
+final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIgCgRhcHBzGAQgASgLMgwuUmVwZWF0ZWRBcHBSBGFwcHM=');

+ 72 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pb.dart

@@ -0,0 +1,72 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_query.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class QueryWorkspaceRequest extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryWorkspaceRequest', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspaceId')
+    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'readApps')
+    ..hasRequiredFields = false
+  ;
+
+  QueryWorkspaceRequest._() : super();
+  factory QueryWorkspaceRequest({
+    $core.String? workspaceId,
+    $core.bool? readApps,
+  }) {
+    final _result = create();
+    if (workspaceId != null) {
+      _result.workspaceId = workspaceId;
+    }
+    if (readApps != null) {
+      _result.readApps = readApps;
+    }
+    return _result;
+  }
+  factory QueryWorkspaceRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory QueryWorkspaceRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  QueryWorkspaceRequest clone() => QueryWorkspaceRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  QueryWorkspaceRequest copyWith(void Function(QueryWorkspaceRequest) updates) => super.copyWith((message) => updates(message as QueryWorkspaceRequest)) as QueryWorkspaceRequest; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static QueryWorkspaceRequest create() => QueryWorkspaceRequest._();
+  QueryWorkspaceRequest createEmptyInstance() => create();
+  static $pb.PbList<QueryWorkspaceRequest> createRepeated() => $pb.PbList<QueryWorkspaceRequest>();
+  @$core.pragma('dart2js:noInline')
+  static QueryWorkspaceRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<QueryWorkspaceRequest>(create);
+  static QueryWorkspaceRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get workspaceId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set workspaceId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasWorkspaceId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearWorkspaceId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.bool get readApps => $_getBF(1);
+  @$pb.TagNumber(2)
+  set readApps($core.bool v) { $_setBool(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasReadApps() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearReadApps() => clearField(2);
+}
+

+ 7 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbenum.dart

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_query.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+

+ 21 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbjson.dart

@@ -0,0 +1,21 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_query.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use queryWorkspaceRequestDescriptor instead')
+const QueryWorkspaceRequest$json = const {
+  '1': 'QueryWorkspaceRequest',
+  '2': const [
+    const {'1': 'workspace_id', '3': 1, '4': 1, '5': 9, '10': 'workspaceId'},
+    const {'1': 'read_apps', '3': 2, '4': 1, '5': 8, '10': 'readApps'},
+  ],
+};
+
+/// Descriptor for `QueryWorkspaceRequest`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List queryWorkspaceRequestDescriptor = $convert.base64Decode('ChVRdWVyeVdvcmtzcGFjZVJlcXVlc3QSIQoMd29ya3NwYWNlX2lkGAEgASgJUgt3b3Jrc3BhY2VJZBIbCglyZWFkX2FwcHMYAiABKAhSCHJlYWRBcHBz');

+ 9 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_query.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'workspace_query.pb.dart';
+

+ 13 - 78
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pb.dart

@@ -9,17 +9,15 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
-import 'workspace_create.pb.dart' as $0;
-
-class UserWorkspace extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserWorkspace', createEmptyInstance: create)
+class CurrentWorkspace extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CurrentWorkspace', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'owner')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspaceId')
     ..hasRequiredFields = false
   ;
 
-  UserWorkspace._() : super();
-  factory UserWorkspace({
+  CurrentWorkspace._() : super();
+  factory CurrentWorkspace({
     $core.String? owner,
     $core.String? workspaceId,
   }) {
@@ -32,26 +30,26 @@ class UserWorkspace extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory UserWorkspace.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory UserWorkspace.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory CurrentWorkspace.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CurrentWorkspace.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  UserWorkspace clone() => UserWorkspace()..mergeFromMessage(this);
+  CurrentWorkspace clone() => CurrentWorkspace()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  UserWorkspace copyWith(void Function(UserWorkspace) updates) => super.copyWith((message) => updates(message as UserWorkspace)) as UserWorkspace; // ignore: deprecated_member_use
+  CurrentWorkspace copyWith(void Function(CurrentWorkspace) updates) => super.copyWith((message) => updates(message as CurrentWorkspace)) as CurrentWorkspace; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static UserWorkspace create() => UserWorkspace._();
-  UserWorkspace createEmptyInstance() => create();
-  static $pb.PbList<UserWorkspace> createRepeated() => $pb.PbList<UserWorkspace>();
+  static CurrentWorkspace create() => CurrentWorkspace._();
+  CurrentWorkspace createEmptyInstance() => create();
+  static $pb.PbList<CurrentWorkspace> createRepeated() => $pb.PbList<CurrentWorkspace>();
   @$core.pragma('dart2js:noInline')
-  static UserWorkspace getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UserWorkspace>(create);
-  static UserWorkspace? _defaultInstance;
+  static CurrentWorkspace getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CurrentWorkspace>(create);
+  static CurrentWorkspace? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get owner => $_getSZ(0);
@@ -72,66 +70,3 @@ class UserWorkspace extends $pb.GeneratedMessage {
   void clearWorkspaceId() => clearField(2);
 }
 
-class UserWorkspaceDetail extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserWorkspaceDetail', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'owner')
-    ..aOM<$0.Workspace>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspace', subBuilder: $0.Workspace.create)
-    ..hasRequiredFields = false
-  ;
-
-  UserWorkspaceDetail._() : super();
-  factory UserWorkspaceDetail({
-    $core.String? owner,
-    $0.Workspace? workspace,
-  }) {
-    final _result = create();
-    if (owner != null) {
-      _result.owner = owner;
-    }
-    if (workspace != null) {
-      _result.workspace = workspace;
-    }
-    return _result;
-  }
-  factory UserWorkspaceDetail.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory UserWorkspaceDetail.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
-  'Will be removed in next major version')
-  UserWorkspaceDetail clone() => UserWorkspaceDetail()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  UserWorkspaceDetail copyWith(void Function(UserWorkspaceDetail) updates) => super.copyWith((message) => updates(message as UserWorkspaceDetail)) as UserWorkspaceDetail; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static UserWorkspaceDetail create() => UserWorkspaceDetail._();
-  UserWorkspaceDetail createEmptyInstance() => create();
-  static $pb.PbList<UserWorkspaceDetail> createRepeated() => $pb.PbList<UserWorkspaceDetail>();
-  @$core.pragma('dart2js:noInline')
-  static UserWorkspaceDetail getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UserWorkspaceDetail>(create);
-  static UserWorkspaceDetail? _defaultInstance;
-
-  @$pb.TagNumber(1)
-  $core.String get owner => $_getSZ(0);
-  @$pb.TagNumber(1)
-  set owner($core.String v) { $_setString(0, v); }
-  @$pb.TagNumber(1)
-  $core.bool hasOwner() => $_has(0);
-  @$pb.TagNumber(1)
-  void clearOwner() => clearField(1);
-
-  @$pb.TagNumber(2)
-  $0.Workspace get workspace => $_getN(1);
-  @$pb.TagNumber(2)
-  set workspace($0.Workspace v) { setField(2, v); }
-  @$pb.TagNumber(2)
-  $core.bool hasWorkspace() => $_has(1);
-  @$pb.TagNumber(2)
-  void clearWorkspace() => clearField(2);
-  @$pb.TagNumber(2)
-  $0.Workspace ensureWorkspace() => $_ensure(1);
-}
-

+ 5 - 16
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pbjson.dart

@@ -8,25 +8,14 @@
 import 'dart:core' as $core;
 import 'dart:convert' as $convert;
 import 'dart:typed_data' as $typed_data;
-@$core.Deprecated('Use userWorkspaceDescriptor instead')
-const UserWorkspace$json = const {
-  '1': 'UserWorkspace',
+@$core.Deprecated('Use currentWorkspaceDescriptor instead')
+const CurrentWorkspace$json = const {
+  '1': 'CurrentWorkspace',
   '2': const [
     const {'1': 'owner', '3': 1, '4': 1, '5': 9, '10': 'owner'},
     const {'1': 'workspace_id', '3': 2, '4': 1, '5': 9, '10': 'workspaceId'},
   ],
 };
 
-/// Descriptor for `UserWorkspace`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List userWorkspaceDescriptor = $convert.base64Decode('Cg1Vc2VyV29ya3NwYWNlEhQKBW93bmVyGAEgASgJUgVvd25lchIhCgx3b3Jrc3BhY2VfaWQYAiABKAlSC3dvcmtzcGFjZUlk');
-@$core.Deprecated('Use userWorkspaceDetailDescriptor instead')
-const UserWorkspaceDetail$json = const {
-  '1': 'UserWorkspaceDetail',
-  '2': const [
-    const {'1': 'owner', '3': 1, '4': 1, '5': 9, '10': 'owner'},
-    const {'1': 'workspace', '3': 2, '4': 1, '5': 11, '6': '.Workspace', '10': 'workspace'},
-  ],
-};
-
-/// Descriptor for `UserWorkspaceDetail`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List userWorkspaceDetailDescriptor = $convert.base64Decode('ChNVc2VyV29ya3NwYWNlRGV0YWlsEhQKBW93bmVyGAEgASgJUgVvd25lchIoCgl3b3Jrc3BhY2UYAiABKAsyCi5Xb3Jrc3BhY2VSCXdvcmtzcGFjZQ==');
+/// Descriptor for `CurrentWorkspace`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List currentWorkspaceDescriptor = $convert.base64Decode('ChBDdXJyZW50V29ya3NwYWNlEhQKBW93bmVyGAEgASgJUgVvd25lchIhCgx3b3Jrc3BhY2VfaWQYAiABKAlSC3dvcmtzcGFjZUlk');

+ 1 - 1
rust-lib/flowy-database/src/lib.rs

@@ -22,7 +22,7 @@ use std::{fmt::Debug, io, path::Path};
 
 pub mod prelude {
     pub use super::UserDatabaseConnection;
-    pub use diesel::{query_dsl::*, ExpressionMethods, RunQueryDsl};
+    pub use diesel::{query_dsl::*, BelongingToDsl, ExpressionMethods, RunQueryDsl};
 }
 
 embed_migrations!("../flowy-database/migrations/");

+ 3 - 2
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -19,12 +19,13 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "CreateAppRequest"
         | "ColorStyle"
         | "App"
+        | "RepeatedApp"
         | "UpdateAppRequest"
         | "UpdateWorkspaceRequest"
         | "CreateWorkspaceRequest"
         | "Workspace"
-        | "UserWorkspace"
-        | "UserWorkspaceDetail"
+        | "QueryWorkspaceRequest"
+        | "CurrentWorkspace"
         | "CreateViewRequest"
         | "View"
         | "WorkspaceError"

+ 1 - 1
rust-lib/flowy-derive/src/proto_buf/serialize.rs

@@ -129,7 +129,7 @@ fn token_stream_for_vec(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type) -> Op
         TypeCategory::Protobuf => Some(quote! {
             pb.#member = ::protobuf::RepeatedField::from_vec(
                 self.#member
-                .iter()
+                .into_iter()
                 .map(|m| m.try_into().unwrap())
                 .collect());
         }),

+ 10 - 4
rust-lib/flowy-sdk/src/deps_resolve/workspace_user_impl.rs

@@ -2,9 +2,9 @@ use flowy_database::DBConnection;
 use flowy_dispatch::prelude::DispatchFuture;
 use flowy_user::prelude::UserSession;
 use flowy_workspace::{
-    entities::workspace::UserWorkspace,
+    entities::workspace::CurrentWorkspace,
     errors::{ErrorBuilder, WorkspaceError, WorkspaceErrorCode},
-    module::WorkspaceUser,
+    module::{WorkspaceDatabase, WorkspaceUser},
 };
 use std::sync::Arc;
 
@@ -35,7 +35,7 @@ impl WorkspaceUser for WorkspaceUserImpl {
         }
     }
 
-    fn get_cur_workspace(&self) -> DispatchFuture<Result<UserWorkspace, WorkspaceError>> {
+    fn get_cur_workspace(&self) -> DispatchFuture<Result<CurrentWorkspace, WorkspaceError>> {
         let user_session = self.user_session.clone();
         DispatchFuture {
             fut: Box::pin(async move {
@@ -45,14 +45,20 @@ impl WorkspaceUser for WorkspaceUserImpl {
                         .build()
                 })?;
 
-                Ok(UserWorkspace {
+                Ok(CurrentWorkspace {
                     owner: user_detail.email,
                     workspace_id: user_detail.workspace,
                 })
             }),
         }
     }
+}
+
+pub struct WorkspaceDatabaseImpl {
+    pub(crate) user_session: Arc<UserSession>,
+}
 
+impl WorkspaceDatabase for WorkspaceDatabaseImpl {
     fn db_connection(&self) -> Result<DBConnection, WorkspaceError> {
         self.user_session.get_db_connection().map_err(|e| {
             ErrorBuilder::new(WorkspaceErrorCode::DatabaseConnectionFail)

+ 6 - 3
rust-lib/flowy-sdk/src/module.rs

@@ -1,9 +1,8 @@
 use crate::flowy_server::{ArcFlowyServer, FlowyServerMocker};
 use flowy_dispatch::prelude::Module;
 use flowy_user::prelude::*;
-use flowy_workspace::prelude::*;
 
-use crate::deps_resolve::WorkspaceUserImpl;
+use crate::deps_resolve::{WorkspaceDatabaseImpl, WorkspaceUserImpl};
 use std::sync::Arc;
 
 pub struct ModuleConfig {
@@ -21,8 +20,12 @@ pub fn build_modules(config: ModuleConfig, _server: ArcFlowyServer) -> Vec<Modul
         user_session: user_session.clone(),
     });
 
+    let workspace_data_impl = Arc::new(WorkspaceDatabaseImpl {
+        user_session: user_session.clone(),
+    });
+
     vec![
         flowy_user::module::create(user_session),
-        flowy_workspace::module::create(workspace_user_impl),
+        flowy_workspace::module::create(workspace_user_impl, workspace_data_impl),
     ]
 }

+ 1 - 1
rust-lib/flowy-test/src/builder.rs

@@ -38,7 +38,7 @@ impl UserTestBuilder {
         builder
     }
 
-    pub fn reset(mut self) -> Self { self.login() }
+    pub fn reset(self) -> Self { self.login() }
 }
 
 pub struct TestBuilder<T: TesterTrait> {

+ 1 - 1
rust-lib/flowy-test/src/tester.rs

@@ -122,7 +122,7 @@ pub trait TesterTrait {
             .unwrap()
         {
             Ok(user_detail) => user_detail,
-            Err(e) => self.login(),
+            Err(_e) => self.login(),
         }
     }
 

+ 1 - 1
rust-lib/flowy-user/src/errors.rs

@@ -68,7 +68,7 @@ impl std::convert::From<flowy_database::result::Error> for UserError {
             .build()
     }
 }
-use diesel::result::DatabaseErrorKind;
+
 impl std::convert::From<flowy_sqlite::Error> for UserError {
     fn from(error: flowy_sqlite::Error) -> Self {
         // match error.kind() {

+ 1 - 1
rust-lib/flowy-user/tests/event/user_status_test.rs

@@ -5,7 +5,7 @@ use serial_test::*;
 #[test]
 #[serial]
 fn user_status_get_failed_before_login() {
-    let a = UserTestBuilder::new()
+    let _a = UserTestBuilder::new()
         .logout()
         .event(GetStatus)
         .assert_error()

+ 2 - 1
rust-lib/flowy-workspace/Cargo.toml

@@ -26,4 +26,5 @@ bincode = { version = "1.3"}
 unicode-segmentation = "1.7.1"
 
 [dev-dependencies]
-flowy-test = { path = "../flowy-test" }
+flowy-test = { path = "../flowy-test" }
+serial_test = "0.5.1"

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

@@ -4,6 +4,7 @@ use crate::{
         workspace::parser::WorkspaceId,
     },
     errors::*,
+    impl_def_and_def_mut,
 };
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
@@ -67,7 +68,7 @@ impl TryInto<CreateAppParams> for CreateAppRequest {
     }
 }
 
-#[derive(ProtoBuf, Default, Debug)]
+#[derive(PartialEq, ProtoBuf, Default, Debug)]
 pub struct App {
     #[pb(index = 1)]
     pub id: String,
@@ -81,3 +82,11 @@ pub struct App {
     #[pb(index = 4)]
     pub desc: String,
 }
+
+#[derive(Debug, Default, ProtoBuf)]
+pub struct RepeatedApp {
+    #[pb(index = 1)]
+    pub items: Vec<App>,
+}
+
+impl_def_and_def_mut!(RepeatedApp, App);

+ 0 - 2
rust-lib/flowy-workspace/src/entities/view/parser/view_thumbnail.rs

@@ -1,5 +1,3 @@
-use unicode_segmentation::UnicodeSegmentation;
-
 #[derive(Debug)]
 pub struct ViewThumbnail(pub String);
 

+ 0 - 1
rust-lib/flowy-workspace/src/entities/view/parser/view_type.rs

@@ -1,5 +1,4 @@
 use crate::{entities::view::ViewTypeIdentifier, sql_tables::view::ViewType};
-use unicode_segmentation::UnicodeSegmentation;
 
 #[derive(Debug)]
 pub struct ViewTypeCheck(pub ViewType);

+ 10 - 1
rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs

@@ -1,4 +1,10 @@
-use crate::{entities::workspace::parser::*, errors::*};
+use crate::{
+    entities::{
+        app::{App, RepeatedApp},
+        workspace::parser::*,
+    },
+    errors::*,
+};
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
 
@@ -43,4 +49,7 @@ pub struct Workspace {
 
     #[pb(index = 3)]
     pub desc: String,
+
+    #[pb(index = 4)]
+    pub apps: RepeatedApp,
 }

+ 35 - 50
rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs

@@ -1,51 +1,36 @@
-// use crate::sql_tables::workspace::Workspace;
-//
-// #[derive(Default, Debug, ProtoBuf)]
-// pub struct WorkspaceDetail {
-//     #[pb(index = 1)]
-//     pub workspace: Workspace,
-//
-//     #[pb(index = 2)]
-//     pub apps: Vec<App>,
-// }
+use crate::{entities::workspace::parser::*, errors::*};
+use flowy_derive::ProtoBuf;
+use std::convert::TryInto;
 
-// use crate::entities::{RepeatedApp, Workspace};
-// use flowy_derive::ProtoBuf;
-// use flowy_traits::cqrs::Identifiable;
-//
-// #[derive(ProtoBuf, Default, Debug)]
-// pub struct WorkspaceQuery {
-//     #[pb(index = 1)]
-//     pub workspace_id: String,
-//
-//     #[pb(index = 2)]
-//     pub read_apps: bool,
-// }
-//
-// impl WorkspaceQuery {
-//     pub fn read_workspace(workspace_id: &str) -> Self {
-//         WorkspaceQuery {
-//             workspace_id: workspace_id.to_string(),
-//             read_apps: false,
-//         }
-//     }
-//
-//     pub fn read_apps(workspace_id: &str) -> Self {
-//         WorkspaceQuery {
-//             workspace_id: workspace_id.to_string(),
-//             read_apps: true,
-//         }
-//     }
-// }
-//
-// #[derive(Default, Debug, ProtoBuf)]
-// pub struct WorkspaceQueryResult {
-//     #[pb(index = 1, oneof)]
-//     pub workspace: Option<Workspace>,
-//
-//     #[pb(index = 2, oneof)]
-//     pub apps: Option<RepeatedApp>,
-//
-//     #[pb(index = 100)]
-//     pub error: String,
-// }
+#[derive(Default, ProtoBuf)]
+pub struct QueryWorkspaceRequest {
+    #[pb(index = 1)]
+    pub workspace_id: String,
+
+    #[pb(index = 2)]
+    pub read_apps: bool,
+}
+
+pub struct QueryWorkspaceParams {
+    pub workspace_id: String,
+    pub read_apps: bool,
+}
+
+impl TryInto<QueryWorkspaceParams> for QueryWorkspaceRequest {
+    type Error = WorkspaceError;
+
+    fn try_into(self) -> Result<QueryWorkspaceParams, Self::Error> {
+        let workspace_id = WorkspaceId::parse(self.workspace_id)
+            .map_err(|e| {
+                ErrorBuilder::new(WorkspaceErrorCode::WorkspaceIdInvalid)
+                    .msg(e)
+                    .build()
+            })?
+            .0;
+
+        Ok(QueryWorkspaceParams {
+            workspace_id,
+            read_apps: self.read_apps,
+        })
+    }
+}

+ 1 - 11
rust-lib/flowy-workspace/src/entities/workspace/workspace_user_detail.rs

@@ -1,20 +1,10 @@
-use crate::entities::workspace::Workspace;
 use flowy_derive::ProtoBuf;
 
 #[derive(ProtoBuf, Default, Debug)]
-pub struct UserWorkspace {
+pub struct CurrentWorkspace {
     #[pb(index = 1)]
     pub owner: String,
 
     #[pb(index = 2)]
     pub workspace_id: String,
 }
-
-#[derive(ProtoBuf, Default, Debug)]
-pub struct UserWorkspaceDetail {
-    #[pb(index = 1)]
-    pub owner: String,
-
-    #[pb(index = 2)]
-    pub workspace: Workspace,
-}

+ 10 - 6
rust-lib/flowy-workspace/src/event.rs

@@ -6,17 +6,21 @@ use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
 pub enum WorkspaceEvent {
     #[display(fmt = "Create workspace")]
     #[event(input = "CreateSpaceRequest", output = "WorkspaceDetail")]
-    CreateWorkspace    = 0,
+    CreateWorkspace = 0,
 
-    #[display(fmt = "Get user's workspace detail")]
-    #[event(output = "UserWorkspaceDetail")]
-    GetWorkspaceDetail = 1,
+    #[display(fmt = "Get user's current workspace")]
+    #[event(output = "Workspace")]
+    GetCurWorkspace = 1,
+
+    #[display(fmt = "Get user's workspace")]
+    #[event(input = "QueryWorkspaceRequest", output = "Workspace")]
+    GetWorkspace    = 2,
 
     #[display(fmt = "Create app")]
     #[event(input = "CreateAppRequest", output = "App")]
-    CreateApp          = 101,
+    CreateApp       = 101,
 
     #[display(fmt = "Create view")]
     #[event(input = "CreateViewRequest", output = "View")]
-    CreateView         = 201,
+    CreateView      = 201,
 }

+ 20 - 11
rust-lib/flowy-workspace/src/handlers/workspace_handler.rs

@@ -1,11 +1,5 @@
 use crate::{
-    entities::workspace::{
-        CreateWorkspaceParams,
-        CreateWorkspaceRequest,
-        UserWorkspace,
-        UserWorkspaceDetail,
-        Workspace,
-    },
+    entities::{app::RepeatedApp, workspace::*},
     errors::WorkspaceError,
     services::WorkspaceController,
 };
@@ -22,9 +16,24 @@ pub async fn create_workspace(
     response_ok(detail)
 }
 
-pub async fn get_workspace_detail(
+pub async fn get_cur_workspace(
     controller: ModuleData<Arc<WorkspaceController>>,
-) -> ResponseResult<UserWorkspaceDetail, WorkspaceError> {
-    let user_workspace = controller.get_user_workspace_detail().await?;
-    response_ok(user_workspace)
+) -> ResponseResult<Workspace, WorkspaceError> {
+    let workspace = controller.get_cur_workspace().await?;
+    response_ok(workspace)
+}
+
+pub async fn get_workspace(
+    data: Data<QueryWorkspaceRequest>,
+    controller: ModuleData<Arc<WorkspaceController>>,
+) -> ResponseResult<Workspace, WorkspaceError> {
+    let params: QueryWorkspaceParams = data.into_inner().try_into()?;
+    let mut workspace = controller.get_workspace(&params.workspace_id).await?;
+
+    if params.read_apps {
+        let apps = controller.get_apps(&params.workspace_id).await?;
+        workspace.apps = RepeatedApp { items: apps };
+    }
+
+    response_ok(workspace)
 }

+ 43 - 0
rust-lib/flowy-workspace/src/macros.rs

@@ -80,3 +80,46 @@ macro_rules! impl_sql_integer_expression {
         }
     };
 }
+// #[macro_export]
+// macro_rules! impl_save_func {
+//     ($func_name:ident, $target:ident, $table_name:expr, $conn:ident) => {
+//         fn $func_name(object: $target) -> Result<(), WorkspaceError> {
+//             let _ = diesel::insert_into($table_name)
+//                 .values($target)
+//                 .execute(&*($conn))?;
+//         }
+//     };
+// }
+
+#[macro_export]
+macro_rules! impl_def_and_def_mut {
+    ($target:ident, $item: ident) => {
+        impl std::ops::Deref for $target {
+            type Target = Vec<$item>;
+
+            fn deref(&self) -> &Self::Target { &self.items }
+        }
+        impl std::ops::DerefMut for $target {
+            fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }
+        }
+
+        impl $target {
+            #[allow(dead_code)]
+            pub fn take_items(&mut self) -> Vec<$item> {
+                ::std::mem::replace(&mut self.items, vec![])
+            }
+
+            #[allow(dead_code)]
+            pub fn push(&mut self, item: $item) {
+                if self.items.contains(&item) {
+                    log::error!("add duplicate item: {:?}", item);
+                    return;
+                }
+
+                self.items.push(item);
+            }
+
+            pub fn first_or_crash(&self) -> &$item { self.items.first().unwrap() }
+        }
+    };
+}

+ 23 - 7
rust-lib/flowy-workspace/src/module.rs

@@ -7,19 +7,34 @@ use crate::{
 };
 use flowy_database::DBConnection;
 
-use crate::{entities::workspace::UserWorkspace, handlers::*, services::ViewController};
+use crate::{entities::workspace::CurrentWorkspace, handlers::*, services::ViewController};
 use std::sync::Arc;
 
+pub trait WorkspaceDeps: WorkspaceUser + WorkspaceDatabase {}
+
 pub trait WorkspaceUser: Send + Sync {
     fn set_cur_workspace_id(&self, id: &str) -> DispatchFuture<Result<(), WorkspaceError>>;
-    fn get_cur_workspace(&self) -> DispatchFuture<Result<UserWorkspace, WorkspaceError>>;
+    fn get_cur_workspace(&self) -> DispatchFuture<Result<CurrentWorkspace, WorkspaceError>>;
+}
+
+pub trait WorkspaceDatabase: Send + Sync {
     fn db_connection(&self) -> Result<DBConnection, WorkspaceError>;
 }
 
-pub fn create(user: Arc<dyn WorkspaceUser>) -> Module {
-    let workspace_controller = Arc::new(WorkspaceController::new(user.clone()));
-    let app_controller = Arc::new(AppController::new(user.clone()));
-    let view_controller = Arc::new(ViewController::new(user.clone()));
+pub fn create(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>) -> Module {
+    let view_controller = Arc::new(ViewController::new(database.clone()));
+
+    let app_controller = Arc::new(AppController::new(
+        user.clone(),
+        database.clone(),
+        view_controller.clone(),
+    ));
+
+    let workspace_controller = Arc::new(WorkspaceController::new(
+        user.clone(),
+        database.clone(),
+        app_controller.clone(),
+    ));
 
     Module::new()
         .name("Flowy-Workspace")
@@ -27,7 +42,8 @@ pub fn create(user: Arc<dyn WorkspaceUser>) -> Module {
         .data(app_controller)
         .data(view_controller)
         .event(WorkspaceEvent::CreateWorkspace, create_workspace)
-        .event(WorkspaceEvent::GetWorkspaceDetail, get_workspace_detail)
+        .event(WorkspaceEvent::GetCurWorkspace, get_cur_workspace)
+        .event(WorkspaceEvent::GetWorkspace, get_workspace)
         .event(WorkspaceEvent::CreateApp, create_app)
         .event(WorkspaceEvent::CreateView, create_view)
 }

+ 202 - 30
rust-lib/flowy-workspace/src/protobuf/model/app_create.rs

@@ -767,6 +767,172 @@ impl ::protobuf::reflect::ProtobufValue for App {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct RepeatedApp {
+    // message fields
+    pub items: ::protobuf::RepeatedField<App>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a RepeatedApp {
+    fn default() -> &'a RepeatedApp {
+        <RepeatedApp as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl RepeatedApp {
+    pub fn new() -> RepeatedApp {
+        ::std::default::Default::default()
+    }
+
+    // repeated .App items = 1;
+
+
+    pub fn get_items(&self) -> &[App] {
+        &self.items
+    }
+    pub fn clear_items(&mut self) {
+        self.items.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_items(&mut self, v: ::protobuf::RepeatedField<App>) {
+        self.items = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_items(&mut self) -> &mut ::protobuf::RepeatedField<App> {
+        &mut self.items
+    }
+
+    // Take field
+    pub fn take_items(&mut self) -> ::protobuf::RepeatedField<App> {
+        ::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new())
+    }
+}
+
+impl ::protobuf::Message for RepeatedApp {
+    fn is_initialized(&self) -> bool {
+        for v in &self.items {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        for value in &self.items {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.items {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> RepeatedApp {
+        RepeatedApp::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<App>>(
+                "items",
+                |m: &RepeatedApp| { &m.items },
+                |m: &mut RepeatedApp| { &mut m.items },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RepeatedApp>(
+                "RepeatedApp",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static RepeatedApp {
+        static instance: ::protobuf::rt::LazyV2<RepeatedApp> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(RepeatedApp::new)
+    }
+}
+
+impl ::protobuf::Clear for RepeatedApp {
+    fn clear(&mut self) {
+        self.items.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for RepeatedApp {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for RepeatedApp {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x10app_create.proto\"\x8b\x01\n\x10CreateAppRequest\x12!\n\x0cworkspa\
     ce_id\x18\x01\x20\x01(\tR\x0bworkspaceId\x12\x12\n\x04name\x18\x02\x20\
@@ -775,36 +941,42 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     ColorStyle\x12\x1f\n\x0btheme_color\x18\x01\x20\x01(\tR\nthemeColor\"`\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\x04descJ\xc9\x04\n\x06\
-    \x12\x04\0\0\x10\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\
-    \x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x18\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\x02\x03\x12\x03\x06\x04\x1f\n\x0c\n\x05\x04\0\x02\x03\x06\
-    \x12\x03\x06\x04\x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0f\x1a\n\
-    \x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x1d\x1e\n\n\n\x02\x04\x01\x12\
-    \x04\x08\0\n\x01\n\n\n\x03\x04\x01\x01\x12\x03\x08\x08\x12\n\x0b\n\x04\
-    \x04\x01\x02\0\x12\x03\t\x04\x1b\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\t\
-    \x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\t\x0b\x16\n\x0c\n\x05\x04\
-    \x01\x02\0\x03\x12\x03\t\x19\x1a\n\n\n\x02\x04\x02\x12\x04\x0b\0\x10\x01\
-    \n\n\n\x03\x04\x02\x01\x12\x03\x0b\x08\x0b\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\x1c\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\x17\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\r\x1a\x1b\
-    \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\x14\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\
-    \x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\x0f\x0b\x0f\n\x0c\
-    \n\x05\x04\x02\x02\x03\x03\x12\x03\x0f\x12\x13b\x06proto3\
+    R\x04name\x12\x12\n\x04desc\x18\x04\x20\x01(\tR\x04desc\")\n\x0bRepeated\
+    App\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.AppR\x05itemsJ\xa6\x05\
+    \n\x06\x12\x04\0\0\x13\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\
+    \0\x12\x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x18\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\x02\x03\x12\x03\x06\x04\x1f\n\x0c\n\x05\x04\0\x02\x03\
+    \x06\x12\x03\x06\x04\x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0f\
+    \x1a\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x1d\x1e\n\n\n\x02\x04\x01\
+    \x12\x04\x08\0\n\x01\n\n\n\x03\x04\x01\x01\x12\x03\x08\x08\x12\n\x0b\n\
+    \x04\x04\x01\x02\0\x12\x03\t\x04\x1b\n\x0c\n\x05\x04\x01\x02\0\x05\x12\
+    \x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\t\x0b\x16\n\x0c\n\x05\
+    \x04\x01\x02\0\x03\x12\x03\t\x19\x1a\n\n\n\x02\x04\x02\x12\x04\x0b\0\x10\
+    \x01\n\n\n\x03\x04\x02\x01\x12\x03\x0b\x08\x0b\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\x1c\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\x17\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\r\x1a\
+    \x1b\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\x14\n\x0c\n\x05\x04\x02\x02\x03\x05\
+    \x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\x0f\x0b\x0f\n\
+    \x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x0f\x12\x13\n\n\n\x02\x04\x03\x12\
+    \x04\x11\0\x13\x01\n\n\n\x03\x04\x03\x01\x12\x03\x11\x08\x13\n\x0b\n\x04\
+    \x04\x03\x02\0\x12\x03\x12\x04\x1b\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\x10\n\x0c\n\x05\
+    \x04\x03\x02\0\x01\x12\x03\x12\x11\x16\n\x0c\n\x05\x04\x03\x02\0\x03\x12\
+    \x03\x12\x19\x1ab\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 22 - 16
rust-lib/flowy-workspace/src/protobuf/model/event.rs

@@ -26,7 +26,8 @@
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum WorkspaceEvent {
     CreateWorkspace = 0,
-    GetWorkspaceDetail = 1,
+    GetCurWorkspace = 1,
+    GetWorkspace = 2,
     CreateApp = 101,
     CreateView = 201,
 }
@@ -39,7 +40,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
     fn from_i32(value: i32) -> ::std::option::Option<WorkspaceEvent> {
         match value {
             0 => ::std::option::Option::Some(WorkspaceEvent::CreateWorkspace),
-            1 => ::std::option::Option::Some(WorkspaceEvent::GetWorkspaceDetail),
+            1 => ::std::option::Option::Some(WorkspaceEvent::GetCurWorkspace),
+            2 => ::std::option::Option::Some(WorkspaceEvent::GetWorkspace),
             101 => ::std::option::Option::Some(WorkspaceEvent::CreateApp),
             201 => ::std::option::Option::Some(WorkspaceEvent::CreateView),
             _ => ::std::option::Option::None
@@ -49,7 +51,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
     fn values() -> &'static [Self] {
         static values: &'static [WorkspaceEvent] = &[
             WorkspaceEvent::CreateWorkspace,
-            WorkspaceEvent::GetWorkspaceDetail,
+            WorkspaceEvent::GetCurWorkspace,
+            WorkspaceEvent::GetWorkspace,
             WorkspaceEvent::CreateApp,
             WorkspaceEvent::CreateView,
         ];
@@ -80,19 +83,22 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*]\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
-    \0\x12\x16\n\x12GetWorkspaceDetail\x10\x01\x12\r\n\tCreateApp\x10e\x12\
-    \x0f\n\nCreateView\x10\xc9\x01J\xce\x01\n\x06\x12\x04\0\0\x07\x01\n\x08\
-    \n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x07\x01\n\n\n\
-    \x03\x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\
-    \x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\x05\
-    \0\x02\0\x02\x12\x03\x03\x16\x17\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\
-    \x04\x1b\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x16\n\x0c\n\x05\
-    \x05\0\x02\x01\x02\x12\x03\x04\x19\x1a\n\x0b\n\x04\x05\0\x02\x02\x12\x03\
-    \x05\x04\x14\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\r\n\x0c\n\x05\
-    \x05\0\x02\x02\x02\x12\x03\x05\x10\x13\n\x0b\n\x04\x05\0\x02\x03\x12\x03\
-    \x06\x04\x15\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x0e\n\x0c\n\
-    \x05\x05\0\x02\x03\x02\x12\x03\x06\x11\x14b\x06proto3\
+    \n\x0bevent.proto*l\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
+    \0\x12\x13\n\x0fGetCurWorkspace\x10\x01\x12\x10\n\x0cGetWorkspace\x10\
+    \x02\x12\r\n\tCreateApp\x10e\x12\x0f\n\nCreateView\x10\xc9\x01J\xf7\x01\
+    \n\x06\x12\x04\0\0\x08\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\
+    \0\x12\x04\x02\0\x08\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\
+    \x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\
+    \x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\x0b\n\x04\
+    \x05\0\x02\x01\x12\x03\x04\x04\x18\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\
+    \x04\x04\x13\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x16\x17\n\x0b\n\
+    \x04\x05\0\x02\x02\x12\x03\x05\x04\x15\n\x0c\n\x05\x05\0\x02\x02\x01\x12\
+    \x03\x05\x04\x10\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\x13\x14\n\x0b\
+    \n\x04\x05\0\x02\x03\x12\x03\x06\x04\x14\n\x0c\n\x05\x05\0\x02\x03\x01\
+    \x12\x03\x06\x04\r\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\x06\x10\x13\n\
+    \x0b\n\x04\x05\0\x02\x04\x12\x03\x07\x04\x15\n\x0c\n\x05\x05\0\x02\x04\
+    \x01\x12\x03\x07\x04\x0e\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x11\
+    \x14b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 3 - 0
rust-lib/flowy-workspace/src/protobuf/model/mod.rs

@@ -9,6 +9,9 @@ pub use workspace_update::*;
 mod app_create; 
 pub use app_create::*; 
 
+mod workspace_query; 
+pub use workspace_query::*; 
+
 mod event; 
 pub use event::*; 
 

+ 83 - 22
rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs

@@ -230,6 +230,7 @@ pub struct Workspace {
     pub id: ::std::string::String,
     pub name: ::std::string::String,
     pub desc: ::std::string::String,
+    pub apps: ::protobuf::SingularPtrField<super::app_create::RepeatedApp>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -323,10 +324,48 @@ impl Workspace {
     pub fn take_desc(&mut self) -> ::std::string::String {
         ::std::mem::replace(&mut self.desc, ::std::string::String::new())
     }
+
+    // .RepeatedApp apps = 4;
+
+
+    pub fn get_apps(&self) -> &super::app_create::RepeatedApp {
+        self.apps.as_ref().unwrap_or_else(|| <super::app_create::RepeatedApp as ::protobuf::Message>::default_instance())
+    }
+    pub fn clear_apps(&mut self) {
+        self.apps.clear();
+    }
+
+    pub fn has_apps(&self) -> bool {
+        self.apps.is_some()
+    }
+
+    // Param is passed by value, moved
+    pub fn set_apps(&mut self, v: super::app_create::RepeatedApp) {
+        self.apps = ::protobuf::SingularPtrField::some(v);
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_apps(&mut self) -> &mut super::app_create::RepeatedApp {
+        if self.apps.is_none() {
+            self.apps.set_default();
+        }
+        self.apps.as_mut().unwrap()
+    }
+
+    // Take field
+    pub fn take_apps(&mut self) -> super::app_create::RepeatedApp {
+        self.apps.take().unwrap_or_else(|| super::app_create::RepeatedApp::new())
+    }
 }
 
 impl ::protobuf::Message for Workspace {
     fn is_initialized(&self) -> bool {
+        for v in &self.apps {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
         true
     }
 
@@ -343,6 +382,9 @@ impl ::protobuf::Message for Workspace {
                 3 => {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.desc)?;
                 },
+                4 => {
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.apps)?;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -364,6 +406,10 @@ impl ::protobuf::Message for Workspace {
         if !self.desc.is_empty() {
             my_size += ::protobuf::rt::string_size(3, &self.desc);
         }
+        if let Some(ref v) = self.apps.as_ref() {
+            let len = v.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -379,6 +425,11 @@ impl ::protobuf::Message for Workspace {
         if !self.desc.is_empty() {
             os.write_string(3, &self.desc)?;
         }
+        if let Some(ref v) = self.apps.as_ref() {
+            os.write_tag(4, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -432,6 +483,11 @@ impl ::protobuf::Message for Workspace {
                 |m: &Workspace| { &m.desc },
                 |m: &mut Workspace| { &mut m.desc },
             ));
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<super::app_create::RepeatedApp>>(
+                "apps",
+                |m: &Workspace| { &m.apps },
+                |m: &mut Workspace| { &mut m.apps },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<Workspace>(
                 "Workspace",
                 fields,
@@ -451,6 +507,7 @@ impl ::protobuf::Clear for Workspace {
         self.id.clear();
         self.name.clear();
         self.desc.clear();
+        self.apps.clear();
         self.unknown_fields.clear();
     }
 }
@@ -468,28 +525,32 @@ impl ::protobuf::reflect::ProtobufValue for Workspace {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x16workspace_create.proto\"@\n\x16CreateWorkspaceRequest\x12\x12\n\
-    \x04name\x18\x01\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x02\x20\x01(\
-    \tR\x04desc\"C\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\x04descJ\xd5\x02\n\x06\x12\x04\0\0\n\x01\n\x08\n\x01\x0c\
-    \x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\
-    \x01\x12\x03\x02\x08\x1e\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x14\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\x0f\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x12\x13\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\n\n\x02\x04\x01\x12\
-    \x04\x06\0\n\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x11\n\x0b\n\x04\
-    \x04\x01\x02\0\x12\x03\x07\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\
-    \x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\r\n\x0c\n\x05\
-    \x04\x01\x02\0\x03\x12\x03\x07\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\
-    \x03\x08\x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x08\x04\n\n\x0c\
-    \n\x05\x04\x01\x02\x01\x01\x12\x03\x08\x0b\x0f\n\x0c\n\x05\x04\x01\x02\
-    \x01\x03\x12\x03\x08\x12\x13\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\t\x04\
-    \x14\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\t\x04\n\n\x0c\n\x05\x04\x01\
-    \x02\x02\x01\x12\x03\t\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\t\
-    \x12\x13b\x06proto3\
+    \n\x16workspace_create.proto\x1a\x10app_create.proto\"@\n\x16CreateWorks\
+    paceRequest\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\x12\x12\n\x04d\
+    esc\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.RepeatedAppR\x04appsJ\x97\x03\n\x06\x12\x04\0\0\x0c\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\x0c\x01\n\n\n\x03\
+    \x04\x01\x01\x12\x03\x07\x08\x11\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x08\
+    \x04\x12\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\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\
+    \x08\x10\x11\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\x0b\n\
+    \x04\x04\x01\x02\x02\x12\x03\n\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\
+    \x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\n\x0b\x0f\n\x0c\
+    \n\x05\x04\x01\x02\x02\x03\x12\x03\n\x12\x13\n\x0b\n\x04\x04\x01\x02\x03\
+    \x12\x03\x0b\x04\x19\n\x0c\n\x05\x04\x01\x02\x03\x06\x12\x03\x0b\x04\x0f\
+    \n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\x0b\x10\x14\n\x0c\n\x05\x04\x01\
+    \x02\x03\x03\x12\x03\x0b\x17\x18b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 243 - 0
rust-lib/flowy-workspace/src/protobuf/model/workspace_query.rs

@@ -0,0 +1,243 @@
+// This file is generated by rust-protobuf 2.22.1. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `workspace_query.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct QueryWorkspaceRequest {
+    // message fields
+    pub workspace_id: ::std::string::String,
+    pub read_apps: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a QueryWorkspaceRequest {
+    fn default() -> &'a QueryWorkspaceRequest {
+        <QueryWorkspaceRequest as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl QueryWorkspaceRequest {
+    pub fn new() -> QueryWorkspaceRequest {
+        ::std::default::Default::default()
+    }
+
+    // string workspace_id = 1;
+
+
+    pub fn get_workspace_id(&self) -> &str {
+        &self.workspace_id
+    }
+    pub fn clear_workspace_id(&mut self) {
+        self.workspace_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_workspace_id(&mut self, v: ::std::string::String) {
+        self.workspace_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_workspace_id(&mut self) -> &mut ::std::string::String {
+        &mut self.workspace_id
+    }
+
+    // Take field
+    pub fn take_workspace_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.workspace_id, ::std::string::String::new())
+    }
+
+    // bool read_apps = 2;
+
+
+    pub fn get_read_apps(&self) -> bool {
+        self.read_apps
+    }
+    pub fn clear_read_apps(&mut self) {
+        self.read_apps = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_read_apps(&mut self, v: bool) {
+        self.read_apps = v;
+    }
+}
+
+impl ::protobuf::Message for QueryWorkspaceRequest {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace_id)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.read_apps = tmp;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.workspace_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.workspace_id);
+        }
+        if self.read_apps != false {
+            my_size += 2;
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.workspace_id.is_empty() {
+            os.write_string(1, &self.workspace_id)?;
+        }
+        if self.read_apps != false {
+            os.write_bool(2, self.read_apps)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> QueryWorkspaceRequest {
+        QueryWorkspaceRequest::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "workspace_id",
+                |m: &QueryWorkspaceRequest| { &m.workspace_id },
+                |m: &mut QueryWorkspaceRequest| { &mut m.workspace_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "read_apps",
+                |m: &QueryWorkspaceRequest| { &m.read_apps },
+                |m: &mut QueryWorkspaceRequest| { &mut m.read_apps },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<QueryWorkspaceRequest>(
+                "QueryWorkspaceRequest",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static QueryWorkspaceRequest {
+        static instance: ::protobuf::rt::LazyV2<QueryWorkspaceRequest> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(QueryWorkspaceRequest::new)
+    }
+}
+
+impl ::protobuf::Clear for QueryWorkspaceRequest {
+    fn clear(&mut self) {
+        self.workspace_id.clear();
+        self.read_apps = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for QueryWorkspaceRequest {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for QueryWorkspaceRequest {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x15workspace_query.proto\"W\n\x15QueryWorkspaceRequest\x12!\n\x0cwork\
+    space_id\x18\x01\x20\x01(\tR\x0bworkspaceId\x12\x1b\n\tread_apps\x18\x02\
+    \x20\x01(\x08R\x08readAppsJ\x98\x01\n\x06\x12\x04\0\0\x05\x01\n\x08\n\
+    \x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\
+    \x04\0\x01\x12\x03\x02\x08\x1d\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\x17\n\x0c\n\x05\x04\0\x02\
+    \x01\x05\x12\x03\x04\x04\x08\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\t\
+    \x12\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x15\x16b\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 31 - 257
rust-lib/flowy-workspace/src/protobuf/model/workspace_user_detail.rs

@@ -24,7 +24,7 @@
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
 
 #[derive(PartialEq,Clone,Default)]
-pub struct UserWorkspace {
+pub struct CurrentWorkspace {
     // message fields
     pub owner: ::std::string::String,
     pub workspace_id: ::std::string::String,
@@ -33,14 +33,14 @@ pub struct UserWorkspace {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a UserWorkspace {
-    fn default() -> &'a UserWorkspace {
-        <UserWorkspace as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a CurrentWorkspace {
+    fn default() -> &'a CurrentWorkspace {
+        <CurrentWorkspace as ::protobuf::Message>::default_instance()
     }
 }
 
-impl UserWorkspace {
-    pub fn new() -> UserWorkspace {
+impl CurrentWorkspace {
+    pub fn new() -> CurrentWorkspace {
         ::std::default::Default::default()
     }
 
@@ -97,7 +97,7 @@ impl UserWorkspace {
     }
 }
 
-impl ::protobuf::Message for UserWorkspace {
+impl ::protobuf::Message for CurrentWorkspace {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -172,8 +172,8 @@ impl ::protobuf::Message for UserWorkspace {
         Self::descriptor_static()
     }
 
-    fn new() -> UserWorkspace {
-        UserWorkspace::new()
+    fn new() -> CurrentWorkspace {
+        CurrentWorkspace::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -182,29 +182,29 @@ impl ::protobuf::Message for UserWorkspace {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "owner",
-                |m: &UserWorkspace| { &m.owner },
-                |m: &mut UserWorkspace| { &mut m.owner },
+                |m: &CurrentWorkspace| { &m.owner },
+                |m: &mut CurrentWorkspace| { &mut m.owner },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "workspace_id",
-                |m: &UserWorkspace| { &m.workspace_id },
-                |m: &mut UserWorkspace| { &mut m.workspace_id },
+                |m: &CurrentWorkspace| { &m.workspace_id },
+                |m: &mut CurrentWorkspace| { &mut m.workspace_id },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UserWorkspace>(
-                "UserWorkspace",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CurrentWorkspace>(
+                "CurrentWorkspace",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static UserWorkspace {
-        static instance: ::protobuf::rt::LazyV2<UserWorkspace> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(UserWorkspace::new)
+    fn default_instance() -> &'static CurrentWorkspace {
+        static instance: ::protobuf::rt::LazyV2<CurrentWorkspace> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CurrentWorkspace::new)
     }
 }
 
-impl ::protobuf::Clear for UserWorkspace {
+impl ::protobuf::Clear for CurrentWorkspace {
     fn clear(&mut self) {
         self.owner.clear();
         self.workspace_id.clear();
@@ -212,255 +212,29 @@ impl ::protobuf::Clear for UserWorkspace {
     }
 }
 
-impl ::std::fmt::Debug for UserWorkspace {
+impl ::std::fmt::Debug for CurrentWorkspace {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for UserWorkspace {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct UserWorkspaceDetail {
-    // message fields
-    pub owner: ::std::string::String,
-    pub workspace: ::protobuf::SingularPtrField<super::workspace_create::Workspace>,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a UserWorkspaceDetail {
-    fn default() -> &'a UserWorkspaceDetail {
-        <UserWorkspaceDetail as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl UserWorkspaceDetail {
-    pub fn new() -> UserWorkspaceDetail {
-        ::std::default::Default::default()
-    }
-
-    // string owner = 1;
-
-
-    pub fn get_owner(&self) -> &str {
-        &self.owner
-    }
-    pub fn clear_owner(&mut self) {
-        self.owner.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_owner(&mut self, v: ::std::string::String) {
-        self.owner = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_owner(&mut self) -> &mut ::std::string::String {
-        &mut self.owner
-    }
-
-    // Take field
-    pub fn take_owner(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.owner, ::std::string::String::new())
-    }
-
-    // .Workspace workspace = 2;
-
-
-    pub fn get_workspace(&self) -> &super::workspace_create::Workspace {
-        self.workspace.as_ref().unwrap_or_else(|| <super::workspace_create::Workspace as ::protobuf::Message>::default_instance())
-    }
-    pub fn clear_workspace(&mut self) {
-        self.workspace.clear();
-    }
-
-    pub fn has_workspace(&self) -> bool {
-        self.workspace.is_some()
-    }
-
-    // Param is passed by value, moved
-    pub fn set_workspace(&mut self, v: super::workspace_create::Workspace) {
-        self.workspace = ::protobuf::SingularPtrField::some(v);
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_workspace(&mut self) -> &mut super::workspace_create::Workspace {
-        if self.workspace.is_none() {
-            self.workspace.set_default();
-        }
-        self.workspace.as_mut().unwrap()
-    }
-
-    // Take field
-    pub fn take_workspace(&mut self) -> super::workspace_create::Workspace {
-        self.workspace.take().unwrap_or_else(|| super::workspace_create::Workspace::new())
-    }
-}
-
-impl ::protobuf::Message for UserWorkspaceDetail {
-    fn is_initialized(&self) -> bool {
-        for v in &self.workspace {
-            if !v.is_initialized() {
-                return false;
-            }
-        };
-        true
-    }
-
-    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        while !is.eof()? {
-            let (field_number, wire_type) = is.read_tag_unpack()?;
-            match field_number {
-                1 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.owner)?;
-                },
-                2 => {
-                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.workspace)?;
-                },
-                _ => {
-                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
-                },
-            };
-        }
-        ::std::result::Result::Ok(())
-    }
-
-    // Compute sizes of nested messages
-    #[allow(unused_variables)]
-    fn compute_size(&self) -> u32 {
-        let mut my_size = 0;
-        if !self.owner.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.owner);
-        }
-        if let Some(ref v) = self.workspace.as_ref() {
-            let len = v.compute_size();
-            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
-        }
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
-    }
-
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.owner.is_empty() {
-            os.write_string(1, &self.owner)?;
-        }
-        if let Some(ref v) = self.workspace.as_ref() {
-            os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-            os.write_raw_varint32(v.get_cached_size())?;
-            v.write_to_with_cached_sizes(os)?;
-        }
-        os.write_unknown_fields(self.get_unknown_fields())?;
-        ::std::result::Result::Ok(())
-    }
-
-    fn get_cached_size(&self) -> u32 {
-        self.cached_size.get()
-    }
-
-    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
-        &self.unknown_fields
-    }
-
-    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
-        &mut self.unknown_fields
-    }
-
-    fn as_any(&self) -> &dyn (::std::any::Any) {
-        self as &dyn (::std::any::Any)
-    }
-    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
-        self as &mut dyn (::std::any::Any)
-    }
-    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
-        self
-    }
-
-    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
-        Self::descriptor_static()
-    }
-
-    fn new() -> UserWorkspaceDetail {
-        UserWorkspaceDetail::new()
-    }
-
-    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "owner",
-                |m: &UserWorkspaceDetail| { &m.owner },
-                |m: &mut UserWorkspaceDetail| { &mut m.owner },
-            ));
-            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<super::workspace_create::Workspace>>(
-                "workspace",
-                |m: &UserWorkspaceDetail| { &m.workspace },
-                |m: &mut UserWorkspaceDetail| { &mut m.workspace },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UserWorkspaceDetail>(
-                "UserWorkspaceDetail",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static UserWorkspaceDetail {
-        static instance: ::protobuf::rt::LazyV2<UserWorkspaceDetail> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(UserWorkspaceDetail::new)
-    }
-}
-
-impl ::protobuf::Clear for UserWorkspaceDetail {
-    fn clear(&mut self) {
-        self.owner.clear();
-        self.workspace.clear();
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for UserWorkspaceDetail {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for UserWorkspaceDetail {
+impl ::protobuf::reflect::ProtobufValue for CurrentWorkspace {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x1bworkspace_user_detail.proto\x1a\x16workspace_create.proto\"H\n\rUs\
-    erWorkspace\x12\x14\n\x05owner\x18\x01\x20\x01(\tR\x05owner\x12!\n\x0cwo\
-    rkspace_id\x18\x02\x20\x01(\tR\x0bworkspaceId\"U\n\x13UserWorkspaceDetai\
-    l\x12\x14\n\x05owner\x18\x01\x20\x01(\tR\x05owner\x12(\n\tworkspace\x18\
-    \x02\x20\x01(\x0b2\n.WorkspaceR\tworkspaceJ\xa9\x02\n\x06\x12\x04\0\0\n\
-    \x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x20\n\
-    \n\n\x02\x04\0\x12\x04\x03\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\
-    \x15\n\x0b\n\x04\x04\0\x02\0\x12\x03\x04\x04\x15\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\x10\n\
-    \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x04\x13\x14\n\x0b\n\x04\x04\0\x02\x01\
-    \x12\x03\x05\x04\x1c\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\x17\n\x0c\n\x05\x04\0\x02\
-    \x01\x03\x12\x03\x05\x1a\x1b\n\n\n\x02\x04\x01\x12\x04\x07\0\n\x01\n\n\n\
-    \x03\x04\x01\x01\x12\x03\x07\x08\x1b\n\x0b\n\x04\x04\x01\x02\0\x12\x03\
-    \x08\x04\x15\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\x10\n\x0c\n\x05\x04\x01\x02\0\x03\x12\
-    \x03\x08\x13\x14\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\t\x04\x1c\n\x0c\n\
-    \x05\x04\x01\x02\x01\x06\x12\x03\t\x04\r\n\x0c\n\x05\x04\x01\x02\x01\x01\
-    \x12\x03\t\x0e\x17\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\t\x1a\x1bb\
-    \x06proto3\
+    \n\x1bworkspace_user_detail.proto\"K\n\x10CurrentWorkspace\x12\x14\n\x05\
+    owner\x18\x01\x20\x01(\tR\x05owner\x12!\n\x0cworkspace_id\x18\x02\x20\
+    \x01(\tR\x0bworkspaceIdJ\x98\x01\n\x06\x12\x04\0\0\x05\x01\n\x08\n\x01\
+    \x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\
+    \0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x15\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\x10\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x13\x14\n\
+    \x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x1c\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\x17\
+    \n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x1a\x1bb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -15,3 +15,6 @@ message App {
     string name = 3;
     string desc = 4;
 }
+message RepeatedApp {
+    repeated App items = 1;
+}

+ 2 - 1
rust-lib/flowy-workspace/src/protobuf/proto/event.proto

@@ -2,7 +2,8 @@ syntax = "proto3";
 
 enum WorkspaceEvent {
     CreateWorkspace = 0;
-    GetWorkspaceDetail = 1;
+    GetCurWorkspace = 1;
+    GetWorkspace = 2;
     CreateApp = 101;
     CreateView = 201;
 }

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

@@ -1,4 +1,5 @@
 syntax = "proto3";
+import "app_create.proto";
 
 message CreateWorkspaceRequest {
     string name = 1;
@@ -8,4 +9,5 @@ message Workspace {
     string id = 1;
     string name = 2;
     string desc = 3;
+    RepeatedApp apps = 4;
 }

+ 6 - 0
rust-lib/flowy-workspace/src/protobuf/proto/workspace_query.proto

@@ -0,0 +1,6 @@
+syntax = "proto3";
+
+message QueryWorkspaceRequest {
+    string workspace_id = 1;
+    bool read_apps = 2;
+}

+ 1 - 6
rust-lib/flowy-workspace/src/protobuf/proto/workspace_user_detail.proto

@@ -1,11 +1,6 @@
 syntax = "proto3";
-import "workspace_create.proto";
 
-message UserWorkspace {
+message CurrentWorkspace {
     string owner = 1;
     string workspace_id = 2;
 }
-message UserWorkspaceDetail {
-    string owner = 1;
-    Workspace workspace = 2;
-}

+ 49 - 12
rust-lib/flowy-workspace/src/services/app_controller.rs

@@ -1,34 +1,71 @@
 use crate::{
-    entities::app::{App, CreateAppParams, *},
+    entities::{
+        app::{App, CreateAppParams, *},
+        view::View,
+    },
     errors::*,
-    module::WorkspaceUser,
-    sql_tables::app::*,
+    module::{WorkspaceDatabase, WorkspaceUser},
+    services::ViewController,
+    sql_tables::app::{AppTable, AppTableChangeset, AppTableSql},
 };
-use flowy_database::{prelude::*, schema::app_table};
+use flowy_dispatch::prelude::DispatchFuture;
 use std::sync::Arc;
 
 pub struct AppController {
     user: Arc<dyn WorkspaceUser>,
+    sql: Arc<AppTableSql>,
+    view_controller: Arc<ViewController>,
 }
 
 impl AppController {
-    pub fn new(user: Arc<dyn WorkspaceUser>) -> Self { Self { user } }
+    pub fn new(
+        user: Arc<dyn WorkspaceUser>,
+        database: Arc<dyn WorkspaceDatabase>,
+        view_controller: Arc<ViewController>,
+    ) -> Self {
+        let sql = Arc::new(AppTableSql { database });
+        Self {
+            user,
+            sql,
+            view_controller,
+        }
+    }
 
     pub fn save_app(&self, params: CreateAppParams) -> Result<App, WorkspaceError> {
         let app_table = AppTable::new(params);
-        let conn = self.user.db_connection()?;
-
         let app: App = app_table.clone().into();
-        let _ = diesel::insert_into(app_table::table)
-            .values(app_table)
-            .execute(&*conn)?;
+        let _ = self.sql.write_app_table(app_table)?;
+
         Ok(app)
     }
 
     pub fn update_app(&self, params: UpdateAppParams) -> Result<(), WorkspaceError> {
         let changeset = AppTableChangeset::new(params);
-        let conn = self.user.db_connection()?;
-        diesel_update_table!(app_table, changeset, conn);
+        let _ = self.sql.update_app_table(changeset)?;
         Ok(())
     }
+
+    pub async fn get_cur_views(&self, app_id: &str) -> Result<Vec<View>, WorkspaceError> {
+        let app_table = self.get_app_table(app_id).await?;
+        let views = self
+            .sql
+            .read_views_belong_to_app(&app_table)?
+            .into_iter()
+            .map(|view_table| view_table.into())
+            .collect::<Vec<View>>();
+
+        Ok(views)
+    }
+
+    fn get_app_table(&self, app_id: &str) -> 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_table(&app_id)?;
+                // TODO: fetch app from remote server
+                Ok(app_table)
+            }),
+        }
+    }
 }

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

@@ -1,27 +1,25 @@
 use crate::{
     entities::view::{CreateViewParams, View},
     errors::WorkspaceError,
-    module::WorkspaceUser,
-    sql_tables::view::ViewTable,
+    module::WorkspaceDatabase,
+    sql_tables::view::{ViewTable, ViewTableSql},
 };
-use flowy_database::{prelude::*, schema::view_table};
 use std::sync::Arc;
 
 pub struct ViewController {
-    user: Arc<dyn WorkspaceUser>,
+    sql: Arc<ViewTableSql>,
 }
 
 impl ViewController {
-    pub fn new(user: Arc<dyn WorkspaceUser>) -> Self { Self { user } }
+    pub fn new(database: Arc<dyn WorkspaceDatabase>) -> Self {
+        let sql = Arc::new(ViewTableSql { database });
+        Self { sql }
+    }
 
     pub async fn save_view(&self, params: CreateViewParams) -> Result<View, WorkspaceError> {
         let view_table = ViewTable::new(params);
-        let conn = self.user.db_connection()?;
         let view: View = view_table.clone().into();
-
-        let _ = diesel::insert_into(view_table::table)
-            .values(view_table)
-            .execute(&*conn)?;
+        let _ = self.sql.write_view_table(view_table)?;
 
         Ok(view)
     }

+ 64 - 34
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -1,16 +1,32 @@
-use crate::{entities::workspace::*, errors::*, module::WorkspaceUser, sql_tables::workspace::*};
-use flowy_database::{prelude::*, schema::workspace_table};
-
-use flowy_database::schema::workspace_table::dsl;
+use crate::{
+    entities::{app::App, workspace::*},
+    errors::*,
+    module::{WorkspaceDatabase, WorkspaceUser},
+    services::AppController,
+    sql_tables::workspace::{WorkspaceSql, WorkspaceTable, WorkspaceTableChangeset},
+};
 use flowy_dispatch::prelude::DispatchFuture;
 use std::sync::Arc;
 
 pub struct WorkspaceController {
     pub user: Arc<dyn WorkspaceUser>,
+    pub sql: Arc<WorkspaceSql>,
+    pub app_controller: Arc<AppController>,
 }
 
 impl WorkspaceController {
-    pub fn new(user: Arc<dyn WorkspaceUser>) -> Self { Self { user } }
+    pub fn new(
+        user: Arc<dyn WorkspaceUser>,
+        database: Arc<dyn WorkspaceDatabase>,
+        app_controller: Arc<AppController>,
+    ) -> Self {
+        let sql = Arc::new(WorkspaceSql { database });
+        Self {
+            user,
+            sql,
+            app_controller,
+        }
+    }
 
     pub async fn save_workspace(
         &self,
@@ -18,49 +34,63 @@ impl WorkspaceController {
     ) -> Result<Workspace, WorkspaceError> {
         let workspace_table = WorkspaceTable::new(params);
         let detail: Workspace = workspace_table.clone().into();
-
-        let _ = diesel::insert_into(workspace_table::table)
-            .values(workspace_table)
-            .execute(&*(self.user.db_connection()?))?;
-
+        let _ = self.sql.write_workspace_table(workspace_table)?;
         let _ = self.user.set_cur_workspace_id(&detail.id).await?;
 
         Ok(detail)
     }
 
-    pub fn get_workspace(
+    pub fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
+        let changeset = WorkspaceTableChangeset::new(params);
+        let _ = self.sql.update_workspace(changeset)?;
+
+        Ok(())
+    }
+
+    pub fn delete_workspace(&self, workspace_id: &str) -> Result<(), WorkspaceError> {
+        unimplemented!()
+    }
+
+    pub async fn get_cur_workspace(&self) -> Result<Workspace, WorkspaceError> {
+        let user_workspace = self.user.get_cur_workspace().await?;
+        let workspace = self.get_workspace(&user_workspace.workspace_id).await?;
+        Ok(workspace)
+    }
+
+    pub async fn get_cur_apps(&self) -> Result<Vec<App>, WorkspaceError> {
+        let user_workspace = self.user.get_cur_workspace().await?;
+        let apps = self.get_apps(&user_workspace.workspace_id).await?;
+        Ok(apps)
+    }
+
+    pub async fn get_workspace(&self, workspace_id: &str) -> Result<Workspace, WorkspaceError> {
+        let workspace_table = self.get_workspace_table(workspace_id).await?;
+        Ok(workspace_table.into())
+    }
+
+    pub async fn get_apps(&self, workspace_id: &str) -> Result<Vec<App>, WorkspaceError> {
+        let apps = self
+            .sql
+            .read_apps_belong_to_workspace(workspace_id)?
+            .into_iter()
+            .map(|app_table| app_table.into())
+            .collect::<Vec<App>>();
+
+        Ok(apps)
+    }
+
+    fn get_workspace_table(
         &self,
         workspace_id: &str,
     ) -> DispatchFuture<Result<WorkspaceTable, WorkspaceError>> {
-        let user = self.user.clone();
+        let sql = self.sql.clone();
         let workspace_id = workspace_id.to_owned();
         DispatchFuture {
             fut: Box::pin(async move {
-                let workspace = dsl::workspace_table
-                    .filter(workspace_table::id.eq(&workspace_id))
-                    .first::<WorkspaceTable>(&*(user.db_connection()?))?;
-
+                let workspace = sql.read_workspace(&workspace_id)?;
                 // TODO: fetch workspace from remote server
                 Ok(workspace)
             }),
         }
     }
-
-    pub fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
-        let changeset = WorkspaceTableChangeset::new(params);
-        let conn = self.user.db_connection()?;
-        diesel_update_table!(workspace_table, changeset, conn);
-
-        Ok(())
-    }
-
-    pub async fn get_user_workspace_detail(&self) -> Result<UserWorkspaceDetail, WorkspaceError> {
-        let user_workspace = self.user.get_cur_workspace().await?;
-        let workspace = self.get_workspace(&user_workspace.workspace_id).await?;
-
-        Ok(UserWorkspaceDetail {
-            owner: user_workspace.owner,
-            workspace: workspace.into(),
-        })
-    }
 }

+ 57 - 0
rust-lib/flowy-workspace/src/sql_tables/app/app_sql.rs

@@ -0,0 +1,57 @@
+use crate::{
+    errors::WorkspaceError,
+    module::WorkspaceDatabase,
+    sql_tables::{
+        app::{AppTable, AppTableChangeset},
+        view::ViewTable,
+        workspace::WorkspaceTable,
+    },
+};
+use flowy_database::{
+    prelude::*,
+    schema::{app_table, app_table::dsl},
+};
+use std::sync::Arc;
+
+pub struct AppTableSql {
+    pub database: Arc<dyn WorkspaceDatabase>,
+}
+
+impl AppTableSql {
+    pub(crate) fn write_app_table(&self, app_table: AppTable) -> Result<(), WorkspaceError> {
+        let conn = self.database.db_connection()?;
+        let _ = diesel::insert_into(app_table::table)
+            .values(app_table)
+            .execute(&*conn)?;
+        Ok(())
+    }
+
+    pub(crate) fn update_app_table(
+        &self,
+        changeset: AppTableChangeset,
+    ) -> Result<(), WorkspaceError> {
+        let conn = self.database.db_connection()?;
+        diesel_update_table!(app_table, changeset, conn);
+        Ok(())
+    }
+
+    pub(crate) fn read_app_table(&self, app_id: &str) -> Result<AppTable, WorkspaceError> {
+        let app_table = dsl::app_table
+            .filter(app_table::id.eq(app_id))
+            .first::<AppTable>(&*(self.database.db_connection()?))?;
+
+        Ok(app_table)
+    }
+
+    pub(crate) fn delete_app(&self, app_id: &str) -> Result<(), WorkspaceError> { unimplemented!() }
+
+    pub(crate) fn read_views_belong_to_app(
+        &self,
+        app_table: &AppTable,
+    ) -> Result<Vec<ViewTable>, WorkspaceError> {
+        let conn = self.database.db_connection()?;
+        let views = ViewTable::belonging_to(app_table).load::<ViewTable>(&*conn)?;
+
+        Ok(views)
+    }
+}

+ 0 - 0
rust-lib/flowy-workspace/src/sql_tables/app/app.rs → rust-lib/flowy-workspace/src/sql_tables/app/app_table.rs


+ 4 - 2
rust-lib/flowy-workspace/src/sql_tables/app/mod.rs

@@ -1,3 +1,5 @@
-mod app;
+mod app_sql;
+mod app_table;
 
-pub use app::*;
+pub use app_sql::*;
+pub use app_table::*;

+ 4 - 2
rust-lib/flowy-workspace/src/sql_tables/view/mod.rs

@@ -1,3 +1,5 @@
-mod view;
+mod view_sql;
+mod view_table;
 
-pub use view::*;
+pub use view_sql::*;
+pub use view_table::*;

+ 23 - 0
rust-lib/flowy-workspace/src/sql_tables/view/view_sql.rs

@@ -0,0 +1,23 @@
+use crate::{
+    errors::WorkspaceError,
+    module::WorkspaceDatabase,
+    sql_tables::{app::AppTable, view::ViewTable},
+};
+use flowy_database::{prelude::*, schema::view_table};
+use std::sync::Arc;
+
+pub struct ViewTableSql {
+    pub database: Arc<dyn WorkspaceDatabase>,
+}
+
+impl ViewTableSql {
+    pub(crate) fn write_view_table(&self, view_table: ViewTable) -> Result<(), WorkspaceError> {
+        let conn = self.database.db_connection()?;
+        let _ = diesel::insert_into(view_table::table)
+            .values(view_table)
+            .execute(&*conn)?;
+        Ok(())
+    }
+
+    pub fn delete_view(&self, view_id: &str) -> Result<(), WorkspaceError> { unimplemented!() }
+}

+ 1 - 1
rust-lib/flowy-workspace/src/sql_tables/view/view.rs → rust-lib/flowy-workspace/src/sql_tables/view/view_table.rs

@@ -4,7 +4,7 @@ use crate::{
     sql_tables::app::AppTable,
 };
 use diesel::sql_types::Integer;
-use flowy_database::schema::{view_table, view_table::dsl};
+use flowy_database::schema::view_table;
 use flowy_infra::{timestamp, uuid};
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]

+ 4 - 2
rust-lib/flowy-workspace/src/sql_tables/workspace/mod.rs

@@ -1,3 +1,5 @@
-mod workspace;
+mod workspace_sql;
+mod workspace_table;
 
-pub use workspace::*;
+pub use workspace_sql::*;
+pub use workspace_table::*;

+ 67 - 0
rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_sql.rs

@@ -0,0 +1,67 @@
+use crate::{
+    errors::WorkspaceError,
+    module::WorkspaceDatabase,
+    sql_tables::{
+        app::AppTable,
+        workspace::{WorkspaceTable, WorkspaceTableChangeset},
+    },
+};
+use flowy_database::{
+    prelude::*,
+    schema::{workspace_table, workspace_table::dsl},
+};
+use std::sync::Arc;
+
+pub struct WorkspaceSql {
+    pub database: Arc<dyn WorkspaceDatabase>,
+}
+
+impl WorkspaceSql {
+    pub fn write_workspace_table(
+        &self,
+        workspace_table: WorkspaceTable,
+    ) -> Result<(), WorkspaceError> {
+        let _ = diesel::insert_into(workspace_table::table)
+            .values(workspace_table)
+            .execute(&*(self.database.db_connection()?))?;
+        Ok(())
+    }
+
+    pub fn read_workspace(&self, workspace_id: &str) -> Result<WorkspaceTable, WorkspaceError> {
+        let workspace = dsl::workspace_table
+            .filter(workspace_table::id.eq(&workspace_id))
+            .first::<WorkspaceTable>(&*(self.database.db_connection()?))?;
+
+        Ok(workspace)
+    }
+
+    pub fn update_workspace(
+        &self,
+        changeset: WorkspaceTableChangeset,
+    ) -> Result<(), WorkspaceError> {
+        let conn = self.database.db_connection()?;
+        diesel_update_table!(workspace_table, changeset, conn);
+        Ok(())
+    }
+
+    pub fn delete_workspace(&self, workspace_id: &str) -> Result<(), WorkspaceError> {
+        unimplemented!()
+    }
+
+    pub(crate) fn read_apps_belong_to_workspace(
+        &self,
+        workspace_id: &str,
+    ) -> Result<Vec<AppTable>, WorkspaceError> {
+        let conn = self.database.db_connection()?;
+
+        let apps = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+            let workspace_table: WorkspaceTable = dsl::workspace_table
+                .filter(workspace_table::id.eq(workspace_id))
+                .first::<WorkspaceTable>(&*(conn))?;
+            let apps = AppTable::belonging_to(&workspace_table).load::<AppTable>(&*conn)?;
+            Ok(apps)
+        })?;
+
+        Ok(apps)
+    }
+}

+ 5 - 1
rust-lib/flowy-workspace/src/sql_tables/workspace/workspace.rs → rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_table.rs

@@ -1,4 +1,7 @@
-use crate::entities::workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, Workspace};
+use crate::entities::{
+    app::RepeatedApp,
+    workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, Workspace},
+};
 use flowy_database::schema::workspace_table;
 use flowy_infra::{timestamp, uuid};
 
@@ -63,6 +66,7 @@ impl std::convert::Into<Workspace> for WorkspaceTable {
             id: self.id,
             name: self.name,
             desc: self.desc,
+            apps: RepeatedApp::default(),
         }
     }
 }

+ 28 - 5
rust-lib/flowy-workspace/tests/event/app_test.rs

@@ -2,20 +2,43 @@ use flowy_test::builder::WorkspaceTestBuilder;
 use flowy_workspace::{
     entities::{
         app::{App, CreateAppRequest},
-        workspace::UserWorkspaceDetail,
+        workspace::Workspace,
     },
-    event::WorkspaceEvent::{CreateApp, GetWorkspaceDetail},
+    event::WorkspaceEvent::{CreateApp, GetCurWorkspace},
 };
 
 #[test]
 fn app_create_success() {
     let user_workspace = WorkspaceTestBuilder::new()
-        .event(GetWorkspaceDetail)
+        .event(GetCurWorkspace)
         .sync_send()
-        .parse::<UserWorkspaceDetail>();
+        .parse::<Workspace>();
 
     let request = CreateAppRequest {
-        workspace_id: user_workspace.workspace.id,
+        workspace_id: user_workspace.id,
+        name: "Github".to_owned(),
+        desc: "AppFlowy Github Project".to_owned(),
+        color_style: Default::default(),
+    };
+
+    let app_detail = WorkspaceTestBuilder::new()
+        .event(CreateApp)
+        .request(request)
+        .sync_send()
+        .parse::<App>();
+
+    dbg!(&app_detail);
+}
+
+#[test]
+fn app_list_from_cur_workspace_test() {
+    let user_workspace = WorkspaceTestBuilder::new()
+        .event(GetCurWorkspace)
+        .sync_send()
+        .parse::<Workspace>();
+
+    let request = CreateAppRequest {
+        workspace_id: user_workspace.id,
         name: "Github".to_owned(),
         desc: "AppFlowy Github Project".to_owned(),
         color_style: Default::default(),

+ 45 - 0
rust-lib/flowy-workspace/tests/event/helper.rs

@@ -1,4 +1,8 @@
 pub use flowy_test::builder::WorkspaceTestBuilder;
+use flowy_workspace::{
+    entities::{app::*, workspace::*},
+    event::WorkspaceEvent::{CreateApp, CreateWorkspace, GetWorkspace},
+};
 
 pub(crate) fn invalid_workspace_name_test_case() -> Vec<String> {
     vec!["", "1234".repeat(100).as_str()]
@@ -6,3 +10,44 @@ pub(crate) fn invalid_workspace_name_test_case() -> Vec<String> {
         .map(|s| s.to_string())
         .collect::<Vec<_>>()
 }
+
+pub fn create_workspace(name: &str, desc: &str) -> Workspace {
+    let request = CreateWorkspaceRequest {
+        name: name.to_owned(),
+        desc: desc.to_owned(),
+    };
+
+    let workspace = WorkspaceTestBuilder::new()
+        .event(CreateWorkspace)
+        .request(request)
+        .sync_send()
+        .parse::<Workspace>();
+
+    workspace
+}
+
+pub fn create_app(name: &str, desc: &str, workspace_id: &str) -> App {
+    let create_app_request = CreateAppRequest {
+        workspace_id: workspace_id.to_string(),
+        name: name.to_string(),
+        desc: desc.to_string(),
+        color_style: Default::default(),
+    };
+
+    let app = WorkspaceTestBuilder::new()
+        .event(CreateApp)
+        .request(create_app_request)
+        .sync_send()
+        .parse::<App>();
+    app
+}
+
+pub fn get_workspace(request: QueryWorkspaceRequest) -> Workspace {
+    let workspace = WorkspaceTestBuilder::new()
+        .event(GetWorkspace)
+        .request(request)
+        .sync_send()
+        .parse::<Workspace>();
+
+    workspace
+}

+ 1 - 0
rust-lib/flowy-workspace/tests/event/main.rs

@@ -1,3 +1,4 @@
 mod app_test;
 mod helper;
+mod view_test;
 mod workspace_test;

+ 0 - 0
rust-lib/flowy-workspace/tests/event/view_test.rs


+ 30 - 30
rust-lib/flowy-workspace/tests/event/workspace_test.rs

@@ -1,54 +1,54 @@
 use crate::helper::*;
 use flowy_workspace::{
-    entities::workspace::{CreateWorkspaceRequest, UserWorkspaceDetail, Workspace},
+    entities::{
+        app::{App, CreateAppRequest},
+        workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, Workspace},
+    },
     event::WorkspaceEvent::*,
     prelude::*,
 };
+use serial_test::*;
 
 #[test]
-fn workspace_create_success() {
-    let request = CreateWorkspaceRequest {
-        name: "123".to_owned(),
-        desc: "".to_owned(),
-    };
-
-    let response = WorkspaceTestBuilder::new()
-        .event(CreateWorkspace)
-        .request(request)
-        .sync_send()
-        .parse::<Workspace>();
-    dbg!(&response);
-}
+fn workspace_create_success() { let _ = create_workspace("First workspace", ""); }
 
 #[test]
 fn workspace_get_detail_success() {
     let user_workspace = WorkspaceTestBuilder::new()
-        .event(GetWorkspaceDetail)
+        .event(GetCurWorkspace)
         .sync_send()
-        .parse::<UserWorkspaceDetail>();
+        .parse::<Workspace>();
 
     dbg!(&user_workspace);
 }
 
 #[test]
-fn workspace_create_and_then_get_detail_success() {
-    let request = CreateWorkspaceRequest {
-        name: "Team A".to_owned(),
-        desc: "Team A Description".to_owned(),
+fn workspace_create_and_then_get_workspace_success() {
+    let workspace = create_workspace(
+        "Workspace A",
+        "workspace_create_and_then_get_workspace_success",
+    );
+    let request = QueryWorkspaceRequest {
+        workspace_id: workspace.id.clone(),
+        read_apps: false,
     };
 
-    let workspace = WorkspaceTestBuilder::new()
-        .event(CreateWorkspace)
-        .request(request)
-        .sync_send()
-        .parse::<Workspace>();
+    let workspace_from_db = get_workspace(request);
+    assert_eq!(workspace.name, workspace_from_db.name);
+}
 
-    let user_workspace = WorkspaceTestBuilder::new()
-        .event(GetWorkspaceDetail)
-        .sync_send()
-        .parse::<UserWorkspaceDetail>();
+#[test]
+fn workspace_create_with_apps_success() {
+    let workspace = create_workspace("Workspace B", "");
+    let app = create_app("App A", "", &workspace.id);
+
+    let query_workspace_request = QueryWorkspaceRequest {
+        workspace_id: workspace.id.clone(),
+        read_apps: true,
+    };
 
-    assert_eq!(workspace.name, user_workspace.workspace.name);
+    let workspace_from_db = get_workspace(query_workspace_request);
+    assert_eq!(&app, workspace_from_db.apps.first_or_crash());
 }
 
 #[test]