Переглянути джерело

fix sqlite database is lock issue by setting busy_timeout and wal mode

appflowy 3 роки тому
батько
коміт
ef4c4180e2
51 змінених файлів з 1539 додано та 700 видалено
  1. 10 10
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart
  2. 6 6
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart
  3. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart
  4. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart
  5. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  6. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  7. 1 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/protobuf.dart
  8. 137 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pb.dart
  9. 7 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pbenum.dart
  10. 32 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pbjson.dart
  11. 9 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pbserver.dart
  12. 2 0
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  13. 2 2
      rust-lib/flowy-dispatch/src/dispatch.rs
  14. 7 8
      rust-lib/flowy-infra/src/kv/kv.rs
  15. 3 0
      rust-lib/flowy-sdk/src/deps_resolve/mod.rs
  16. 63 0
      rust-lib/flowy-sdk/src/deps_resolve/workspace_user_impl.rs
  17. 6 1
      rust-lib/flowy-sdk/src/lib.rs
  18. 2 42
      rust-lib/flowy-sdk/src/module.rs
  19. 30 0
      rust-lib/flowy-sqlite/src/conn_ext.rs
  20. 3 0
      rust-lib/flowy-sqlite/src/lib.rs
  21. 49 2
      rust-lib/flowy-sqlite/src/pool.rs
  22. 186 0
      rust-lib/flowy-sqlite/src/pragma.rs
  23. 14 3
      rust-lib/flowy-test/src/builder.rs
  24. 12 2
      rust-lib/flowy-test/src/tester.rs
  25. 7 7
      rust-lib/flowy-user/src/errors.rs
  26. 60 60
      rust-lib/flowy-user/src/protobuf/model/errors.rs
  27. 5 5
      rust-lib/flowy-user/src/protobuf/proto/errors.proto
  28. 7 6
      rust-lib/flowy-user/src/services/user_session/database.rs
  29. 11 13
      rust-lib/flowy-user/src/services/user_session/user_session.rs
  30. 0 230
      rust-lib/flowy-user/tests/event/sign_in_test.rs
  31. 65 65
      rust-lib/flowy-user/tests/event/sign_up_test.rs
  32. 36 36
      rust-lib/flowy-user/tests/event/user_status_test.rs
  33. 140 140
      rust-lib/flowy-user/tests/event/user_update_test.rs
  34. 18 4
      rust-lib/flowy-workspace/src/entities/workspace/workspace_user_detail.rs
  35. 2 10
      rust-lib/flowy-workspace/src/errors.rs
  36. 4 4
      rust-lib/flowy-workspace/src/event.rs
  37. 11 8
      rust-lib/flowy-workspace/src/handlers/workspace_handler.rs
  38. 4 4
      rust-lib/flowy-workspace/src/module.rs
  39. 8 8
      rust-lib/flowy-workspace/src/protobuf/model/errors.rs
  40. 11 11
      rust-lib/flowy-workspace/src/protobuf/model/event.rs
  41. 3 0
      rust-lib/flowy-workspace/src/protobuf/model/mod.rs
  42. 476 0
      rust-lib/flowy-workspace/src/protobuf/model/workspace_user_detail.rs
  43. 1 1
      rust-lib/flowy-workspace/src/protobuf/proto/errors.proto
  44. 1 1
      rust-lib/flowy-workspace/src/protobuf/proto/event.proto
  45. 11 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_user_detail.proto
  46. 31 1
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  47. 22 1
      rust-lib/flowy-workspace/tests/event/workspace_test.rs
  48. 2 1
      scripts/flowy-tool/Cargo.toml
  49. 1 0
      scripts/flowy-tool/src/proto/ast.rs
  50. 2 0
      scripts/flowy-tool/src/proto/proto_info.rs
  51. 11 0
      scripts/flowy-tool/src/util/file.rs

+ 10 - 10
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart

@@ -11,11 +11,11 @@ import 'package:protobuf/protobuf.dart' as $pb;
 
 class UserErrorCode extends $pb.ProtobufEnum {
   static const UserErrorCode Unknown = UserErrorCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
-  static const UserErrorCode DatabaseInitFailed = UserErrorCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseInitFailed');
-  static const UserErrorCode DatabaseWriteLocked = UserErrorCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseWriteLocked');
-  static const UserErrorCode DatabaseReadLocked = UserErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseReadLocked');
-  static const UserErrorCode DatabaseUserDidNotMatch = UserErrorCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseUserDidNotMatch');
-  static const UserErrorCode DatabaseInternalError = UserErrorCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseInternalError');
+  static const UserErrorCode UserDatabaseInitFailed = UserErrorCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInitFailed');
+  static const UserErrorCode UserDatabaseWriteLocked = UserErrorCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseWriteLocked');
+  static const UserErrorCode UserDatabaseReadLocked = UserErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseReadLocked');
+  static const UserErrorCode UserDatabaseDidNotMatch = UserErrorCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseDidNotMatch');
+  static const UserErrorCode UserDatabaseInternalError = UserErrorCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInternalError');
   static const UserErrorCode UserNotLoginYet = UserErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
   static const UserErrorCode ReadCurrentIdFailed = UserErrorCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadCurrentIdFailed');
   static const UserErrorCode WriteCurrentIdFailed = UserErrorCode._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WriteCurrentIdFailed');
@@ -27,11 +27,11 @@ class UserErrorCode extends $pb.ProtobufEnum {
 
   static const $core.List<UserErrorCode> values = <UserErrorCode> [
     Unknown,
-    DatabaseInitFailed,
-    DatabaseWriteLocked,
-    DatabaseReadLocked,
-    DatabaseUserDidNotMatch,
-    DatabaseInternalError,
+    UserDatabaseInitFailed,
+    UserDatabaseWriteLocked,
+    UserDatabaseReadLocked,
+    UserDatabaseDidNotMatch,
+    UserDatabaseInternalError,
     UserNotLoginYet,
     ReadCurrentIdFailed,
     WriteCurrentIdFailed,

+ 6 - 6
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart

@@ -13,11 +13,11 @@ const UserErrorCode$json = const {
   '1': 'UserErrorCode',
   '2': const [
     const {'1': 'Unknown', '2': 0},
-    const {'1': 'DatabaseInitFailed', '2': 1},
-    const {'1': 'DatabaseWriteLocked', '2': 2},
-    const {'1': 'DatabaseReadLocked', '2': 3},
-    const {'1': 'DatabaseUserDidNotMatch', '2': 4},
-    const {'1': 'DatabaseInternalError', '2': 5},
+    const {'1': 'UserDatabaseInitFailed', '2': 1},
+    const {'1': 'UserDatabaseWriteLocked', '2': 2},
+    const {'1': 'UserDatabaseReadLocked', '2': 3},
+    const {'1': 'UserDatabaseDidNotMatch', '2': 4},
+    const {'1': 'UserDatabaseInternalError', '2': 5},
     const {'1': 'UserNotLoginYet', '2': 10},
     const {'1': 'ReadCurrentIdFailed', '2': 11},
     const {'1': 'WriteCurrentIdFailed', '2': 12},
@@ -30,7 +30,7 @@ const UserErrorCode$json = const {
 };
 
 /// Descriptor for `UserErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userErrorCodeDescriptor = $convert.base64Decode('Cg1Vc2VyRXJyb3JDb2RlEgsKB1Vua25vd24QABIWChJEYXRhYmFzZUluaXRGYWlsZWQQARIXChNEYXRhYmFzZVdyaXRlTG9ja2VkEAISFgoSRGF0YWJhc2VSZWFkTG9ja2VkEAMSGwoXRGF0YWJhc2VVc2VyRGlkTm90TWF0Y2gQBBIZChVEYXRhYmFzZUludGVybmFsRXJyb3IQBRITCg9Vc2VyTm90TG9naW5ZZXQQChIXChNSZWFkQ3VycmVudElkRmFpbGVkEAsSGAoUV3JpdGVDdXJyZW50SWRGYWlsZWQQDBIQCgxFbWFpbEludmFsaWQQFBITCg9QYXNzd29yZEludmFsaWQQFRITCg9Vc2VyTmFtZUludmFsaWQQFhIYChRVc2VyV29ya3NwYWNlSW52YWxpZBAXEhEKDVVzZXJJZEludmFsaWQQGA==');
+final $typed_data.Uint8List userErrorCodeDescriptor = $convert.base64Decode('Cg1Vc2VyRXJyb3JDb2RlEgsKB1Vua25vd24QABIaChZVc2VyRGF0YWJhc2VJbml0RmFpbGVkEAESGwoXVXNlckRhdGFiYXNlV3JpdGVMb2NrZWQQAhIaChZVc2VyRGF0YWJhc2VSZWFkTG9ja2VkEAMSGwoXVXNlckRhdGFiYXNlRGlkTm90TWF0Y2gQBBIdChlVc2VyRGF0YWJhc2VJbnRlcm5hbEVycm9yEAUSEwoPVXNlck5vdExvZ2luWWV0EAoSFwoTUmVhZEN1cnJlbnRJZEZhaWxlZBALEhgKFFdyaXRlQ3VycmVudElkRmFpbGVkEAwSEAoMRW1haWxJbnZhbGlkEBQSEwoPUGFzc3dvcmRJbnZhbGlkEBUSEwoPVXNlck5hbWVJbnZhbGlkEBYSGAoUVXNlcldvcmtzcGFjZUludmFsaWQQFxIRCg1Vc2VySWRJbnZhbGlkEBg=');
 @$core.Deprecated('Use userErrorDescriptor instead')
 const UserError$json = const {
   '1': 'UserError',

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

@@ -16,7 +16,7 @@ class WorkspaceErrorCode extends $pb.ProtobufEnum {
   static const WorkspaceErrorCode AppColorStyleInvalid = WorkspaceErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppColorStyleInvalid');
   static const WorkspaceErrorCode AppIdInvalid = WorkspaceErrorCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppIdInvalid');
   static const WorkspaceErrorCode DatabaseConnectionFail = WorkspaceErrorCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseConnectionFail');
-  static const WorkspaceErrorCode DatabaseInternalError = WorkspaceErrorCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseInternalError');
+  static const WorkspaceErrorCode WorkspaceDatabaseError = WorkspaceErrorCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDatabaseError');
   static const WorkspaceErrorCode UserInternalError = WorkspaceErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserInternalError');
   static const WorkspaceErrorCode UserNotLoginYet = WorkspaceErrorCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
 
@@ -27,7 +27,7 @@ class WorkspaceErrorCode extends $pb.ProtobufEnum {
     AppColorStyleInvalid,
     AppIdInvalid,
     DatabaseConnectionFail,
-    DatabaseInternalError,
+    WorkspaceDatabaseError,
     UserInternalError,
     UserNotLoginYet,
   ];

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

@@ -18,14 +18,14 @@ const WorkspaceErrorCode$json = const {
     const {'1': 'AppColorStyleInvalid', '2': 3},
     const {'1': 'AppIdInvalid', '2': 4},
     const {'1': 'DatabaseConnectionFail', '2': 5},
-    const {'1': 'DatabaseInternalError', '2': 6},
+    const {'1': 'WorkspaceDatabaseError', '2': 6},
     const {'1': 'UserInternalError', '2': 10},
     const {'1': 'UserNotLoginYet', '2': 11},
   ],
 };
 
 /// Descriptor for `WorkspaceErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceErrorCodeDescriptor = $convert.base64Decode('ChJXb3Jrc3BhY2VFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQBBIaChZEYXRhYmFzZUNvbm5lY3Rpb25GYWlsEAUSGQoVRGF0YWJhc2VJbnRlcm5hbEVycm9yEAYSFQoRVXNlckludGVybmFsRXJyb3IQChITCg9Vc2VyTm90TG9naW5ZZXQQCw==');
+final $typed_data.Uint8List workspaceErrorCodeDescriptor = $convert.base64Decode('ChJXb3Jrc3BhY2VFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQBBIaChZEYXRhYmFzZUNvbm5lY3Rpb25GYWlsEAUSGgoWV29ya3NwYWNlRGF0YWJhc2VFcnJvchAGEhUKEVVzZXJJbnRlcm5hbEVycm9yEAoSEwoPVXNlck5vdExvZ2luWWV0EAs=');
 @$core.Deprecated('Use workspaceErrorDescriptor instead')
 const WorkspaceError$json = const {
   '1': 'WorkspaceError',

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

@@ -11,11 +11,11 @@ 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 GetWorkspaceUserDetail = WorkspaceEvent._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspaceUserDetail');
+  static const WorkspaceEvent GetWorkspaceDetail = WorkspaceEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspaceDetail');
 
   static const $core.List<WorkspaceEvent> values = <WorkspaceEvent> [
     CreateWorkspace,
-    GetWorkspaceUserDetail,
+    GetWorkspaceDetail,
   ];
 
   static final $core.Map<$core.int, WorkspaceEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -13,9 +13,9 @@ const WorkspaceEvent$json = const {
   '1': 'WorkspaceEvent',
   '2': const [
     const {'1': 'CreateWorkspace', '2': 0},
-    const {'1': 'GetWorkspaceUserDetail', '2': 100},
+    const {'1': 'GetWorkspaceDetail', '2': 1},
   ],
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIaChZHZXRXb3Jrc3BhY2VVc2VyRGV0YWlsEGQ=');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIWChJHZXRXb3Jrc3BhY2VEZXRhaWwQAQ==');

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

@@ -3,5 +3,6 @@ export './errors.pb.dart';
 export './workspace_update.pb.dart';
 export './app_create.pb.dart';
 export './event.pb.dart';
+export './workspace_user_detail.pb.dart';
 export './workspace_create.pb.dart';
 export './app_update.pb.dart';

+ 137 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pb.dart

@@ -0,0 +1,137 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_user_detail.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;
+
+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)
+    ..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({
+    $core.String? owner,
+    $core.String? workspaceId,
+  }) {
+    final _result = create();
+    if (owner != null) {
+      _result.owner = owner;
+    }
+    if (workspaceId != null) {
+      _result.workspaceId = workspaceId;
+    }
+    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);
+  @$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);
+  @$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
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static UserWorkspace create() => UserWorkspace._();
+  UserWorkspace createEmptyInstance() => create();
+  static $pb.PbList<UserWorkspace> createRepeated() => $pb.PbList<UserWorkspace>();
+  @$core.pragma('dart2js:noInline')
+  static UserWorkspace getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UserWorkspace>(create);
+  static UserWorkspace? _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)
+  $core.String get workspaceId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set workspaceId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasWorkspaceId() => $_has(1);
+  @$pb.TagNumber(2)
+  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.WorkspaceDetail>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspace', subBuilder: $0.WorkspaceDetail.create)
+    ..hasRequiredFields = false
+  ;
+
+  UserWorkspaceDetail._() : super();
+  factory UserWorkspaceDetail({
+    $core.String? owner,
+    $0.WorkspaceDetail? 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.WorkspaceDetail get workspace => $_getN(1);
+  @$pb.TagNumber(2)
+  set workspace($0.WorkspaceDetail v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasWorkspace() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearWorkspace() => clearField(2);
+  @$pb.TagNumber(2)
+  $0.WorkspaceDetail ensureWorkspace() => $_ensure(1);
+}
+

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

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_user_detail.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
+

+ 32 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pbjson.dart

@@ -0,0 +1,32 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_user_detail.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 userWorkspaceDescriptor instead')
+const UserWorkspace$json = const {
+  '1': 'UserWorkspace',
+  '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': '.WorkspaceDetail', '10': 'workspace'},
+  ],
+};
+
+/// Descriptor for `UserWorkspaceDetail`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List userWorkspaceDetailDescriptor = $convert.base64Decode('ChNVc2VyV29ya3NwYWNlRGV0YWlsEhQKBW93bmVyGAEgASgJUgVvd25lchIuCgl3b3Jrc3BhY2UYAiABKAsyEC5Xb3Jrc3BhY2VEZXRhaWxSCXdvcmtzcGFjZQ==');

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

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: workspace_user_detail.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_user_detail.pb.dart';
+

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

@@ -23,6 +23,8 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "UpdateWorkspaceRequest"
         | "CreateWorkspaceRequest"
         | "WorkspaceDetail"
+        | "UserWorkspace"
+        | "UserWorkspaceDetail"
         | "WorkspaceError"
         | "FFIRequest"
         | "FFIResponse"

+ 2 - 2
rust-lib/flowy-dispatch/src/dispatch.rs

@@ -99,14 +99,14 @@ impl EventDispatch {
 }
 
 #[pin_project]
-pub struct DispatchFuture<T: Responder + Send + Sync> {
+pub struct DispatchFuture<T: Send + Sync> {
     #[pin]
     pub fut: Pin<Box<dyn Future<Output = T> + Sync + Send>>,
 }
 
 impl<T> Future for DispatchFuture<T>
 where
-    T: Responder + Send + Sync,
+    T: Send + Sync,
 {
     type Output = T;
 

+ 7 - 8
rust-lib/flowy-infra/src/kv/kv.rs

@@ -18,22 +18,21 @@ pub struct KVStore {
 impl KVStore {
     fn new() -> Self { KVStore { database: None } }
 
-    pub fn set(item: KeyValue) -> Result<(), String> {
-        let conn = get_connection()?;
+    fn set(item: KeyValue) -> Result<(), String> {
         let _ = diesel::replace_into(kv_table::table)
             .values(&item)
-            .execute(&*conn)
-            .map_err(|e| format!("{:?}", e))?;
+            .execute(&*(get_connection()?))
+            .map_err(|e| format!("KV set error: {:?}", e))?;
 
         Ok(())
     }
 
-    pub fn get(key: &str) -> Result<KeyValue, String> {
+    fn get(key: &str) -> Result<KeyValue, String> {
         let conn = get_connection()?;
         let item = dsl::kv_table
             .filter(kv_table::key.eq(key))
             .first::<KeyValue>(&*conn)
-            .map_err(|e| format!("{:?}", e))?;
+            .map_err(|e| format!("KV get error: {:?}", e))?;
         Ok(item)
     }
 
@@ -43,7 +42,7 @@ impl KVStore {
         let sql = dsl::kv_table.filter(kv_table::key.eq(key));
         let _ = diesel::delete(sql)
             .execute(&*conn)
-            .map_err(|e| format!("{:?}", e))?;
+            .map_err(|e| format!("KV remove error: {:?}", e))?;
         Ok(())
     }
 
@@ -125,7 +124,7 @@ fn get_connection() -> Result<DBConnection, String> {
                 .as_ref()
                 .expect("KVStore is not init")
                 .get_connection()
-                .map_err(|e| format!("{:?}", e))?;
+                .map_err(|e| format!("KVStore error: {:?}", e))?;
             Ok(conn)
         },
         Err(e) => {

+ 3 - 0
rust-lib/flowy-sdk/src/deps_resolve/mod.rs

@@ -0,0 +1,3 @@
+mod workspace_user_impl;
+
+pub use workspace_user_impl::*;

+ 63 - 0
rust-lib/flowy-sdk/src/deps_resolve/workspace_user_impl.rs

@@ -0,0 +1,63 @@
+use flowy_database::DBConnection;
+use flowy_dispatch::prelude::DispatchFuture;
+use flowy_user::prelude::UserSession;
+use flowy_workspace::{
+    entities::workspace::UserWorkspace,
+    errors::{ErrorBuilder, WorkspaceError, WorkspaceErrorCode},
+    module::WorkspaceUser,
+};
+use std::sync::Arc;
+
+pub struct WorkspaceUserImpl {
+    pub(crate) user_session: Arc<UserSession>,
+}
+
+impl WorkspaceUser for WorkspaceUserImpl {
+    fn set_cur_workspace_id(
+        &self,
+        workspace_id: &str,
+    ) -> DispatchFuture<Result<(), WorkspaceError>> {
+        let user_session = self.user_session.clone();
+        let workspace_id = workspace_id.to_owned();
+        DispatchFuture {
+            fut: Box::pin(async move {
+                let _ = user_session
+                    .set_current_workspace(&workspace_id)
+                    .await
+                    .map_err(|e| {
+                        ErrorBuilder::new(WorkspaceErrorCode::UserInternalError)
+                            .error(e)
+                            .build()
+                    });
+
+                Ok(())
+            }),
+        }
+    }
+
+    fn get_cur_workspace(&self) -> DispatchFuture<Result<UserWorkspace, WorkspaceError>> {
+        let user_session = self.user_session.clone();
+        DispatchFuture {
+            fut: Box::pin(async move {
+                let user_detail = user_session.user_detail().map_err(|e| {
+                    ErrorBuilder::new(WorkspaceErrorCode::UserNotLoginYet)
+                        .error(e)
+                        .build()
+                })?;
+
+                Ok(UserWorkspace {
+                    owner: user_detail.email,
+                    workspace_id: user_detail.workspace,
+                })
+            }),
+        }
+    }
+
+    fn db_connection(&self) -> Result<DBConnection, WorkspaceError> {
+        self.user_session.get_db_connection().map_err(|e| {
+            ErrorBuilder::new(WorkspaceErrorCode::DatabaseConnectionFail)
+                .error(e)
+                .build()
+        })
+    }
+}

+ 6 - 1
rust-lib/flowy-sdk/src/lib.rs

@@ -1,7 +1,9 @@
+mod deps_resolve;
 mod flowy_server;
 pub mod module;
 
 pub use crate::flowy_server::{ArcFlowyServer, FlowyServerMocker};
+use deps_resolve::*;
 use flowy_dispatch::prelude::*;
 use module::build_modules;
 pub use module::*;
@@ -31,7 +33,10 @@ impl FlowySDK {
         FlowySDK::init_log(root);
 
         tracing::info!("🔥 Root path: {}", root);
-        let _ = flowy_infra::kv::KVStore::init(root);
+        match flowy_infra::kv::KVStore::init(root) {
+            Ok(_) => {},
+            Err(e) => tracing::error!("Init kv store failedL: {}", e),
+        }
         FlowySDK::init_modules(root, server);
     }
 

+ 2 - 42
rust-lib/flowy-sdk/src/module.rs

@@ -4,6 +4,8 @@ use flowy_dispatch::prelude::{DispatchFuture, Module};
 use flowy_user::prelude::*;
 use flowy_workspace::prelude::*;
 
+use crate::deps_resolve::WorkspaceUserImpl;
+use flowy_workspace::entities::workspace::UserWorkspace;
 use std::sync::Arc;
 
 pub struct ModuleConfig {
@@ -26,45 +28,3 @@ pub fn build_modules(config: ModuleConfig, _server: ArcFlowyServer) -> Vec<Modul
         flowy_workspace::module::create(workspace_user_impl),
     ]
 }
-
-pub struct WorkspaceUserImpl {
-    user_session: Arc<UserSession>,
-}
-
-impl WorkspaceUser for WorkspaceUserImpl {
-    fn set_workspace(&self, workspace_id: &str) -> DispatchFuture<Result<(), WorkspaceError>> {
-        let user_session = self.user_session.clone();
-        let workspace_id = workspace_id.to_owned();
-        DispatchFuture {
-            fut: Box::pin(async move {
-                let _ = user_session
-                    .set_current_workspace(&workspace_id)
-                    .await
-                    .map_err(|e| {
-                        ErrorBuilder::new(WorkspaceErrorCode::UserInternalError)
-                            .error(e)
-                            .build()
-                    });
-
-                Ok(())
-            }),
-        }
-    }
-
-    fn get_workspace(&self) -> Result<String, WorkspaceError> {
-        let user_detail = self.user_session.user_detail().map_err(|e| {
-            ErrorBuilder::new(WorkspaceErrorCode::UserNotLoginYet)
-                .error(e)
-                .build()
-        })?;
-        Ok(user_detail.id)
-    }
-
-    fn db_connection(&self) -> Result<DBConnection, WorkspaceError> {
-        self.user_session.get_db_connection().map_err(|e| {
-            ErrorBuilder::new(WorkspaceErrorCode::DatabaseConnectionFail)
-                .error(e)
-                .build()
-        })
-    }
-}

+ 30 - 0
rust-lib/flowy-sqlite/src/conn_ext.rs

@@ -0,0 +1,30 @@
+use crate::errors::*;
+use diesel::{
+    dsl::sql,
+    expression::SqlLiteral,
+    query_dsl::LoadQuery,
+    Connection,
+    RunQueryDsl,
+    SqliteConnection,
+};
+
+pub trait ConnectionExtension: Connection {
+    fn query<ST, T>(&self, query: &str) -> Result<T>
+    where
+        SqlLiteral<ST>: LoadQuery<SqliteConnection, T>;
+
+    fn exec(&self, query: impl AsRef<str>) -> Result<usize>;
+}
+
+impl ConnectionExtension for SqliteConnection {
+    fn query<ST, T>(&self, query: &str) -> Result<T>
+    where
+        SqlLiteral<ST>: LoadQuery<SqliteConnection, T>,
+    {
+        Ok(sql::<ST>(query).get_result(self)?)
+    }
+
+    fn exec(&self, query: impl AsRef<str>) -> Result<usize> {
+        Ok(SqliteConnection::execute(self, query.as_ref())?)
+    }
+}

+ 3 - 0
rust-lib/flowy-sqlite/src/lib.rs

@@ -1,7 +1,10 @@
+mod conn_ext;
 mod database;
 #[allow(deprecated, clippy::large_enum_variant)]
 mod errors;
 mod pool;
+mod pragma;
+
 pub use database::*;
 pub use pool::*;
 

+ 49 - 2
rust-lib/flowy-sqlite/src/pool.rs

@@ -1,6 +1,6 @@
-use crate::errors::*;
+use crate::{conn_ext::*, errors::*, pragma::*};
 use diesel::{connection::Connection, SqliteConnection};
-use r2d2::{ManageConnection, Pool};
+use r2d2::{CustomizeConnection, ManageConnection, Pool};
 use scheduled_thread_pool::ScheduledThreadPool;
 use std::{
     sync::{
@@ -34,10 +34,12 @@ impl ConnectionPool {
         let manager = ConnectionManager::new(uri);
         let thread_pool = DB_POOL.clone();
         let config = Arc::new(config);
+        let customizer_config = DatabaseCustomizerConfig::default();
 
         let pool = r2d2::Pool::builder()
             .thread_pool(thread_pool)
             .min_idle(Some(config.min_idle))
+            .connection_customizer(Box::new(DatabaseCustomizer::new(customizer_config)))
             .max_size(config.max_size)
             .max_lifetime(None)
             .connection_timeout(config.connection_timeout)
@@ -127,3 +129,48 @@ impl ManageConnection for ConnectionManager {
 impl ConnectionManager {
     pub fn new<S: Into<String>>(uri: S) -> Self { ConnectionManager { db_uri: uri.into() } }
 }
+
+#[derive(Debug)]
+pub struct DatabaseCustomizerConfig {
+    pub(crate) journal_mode: SQLiteJournalMode,
+    pub(crate) synchronous: SQLiteSynchronous,
+    pub(crate) busy_timeout: i32,
+    pub(crate) secure_delete: bool,
+}
+
+impl Default for DatabaseCustomizerConfig {
+    fn default() -> Self {
+        Self {
+            journal_mode: SQLiteJournalMode::WAL,
+            synchronous: SQLiteSynchronous::NORMAL,
+            busy_timeout: 5000,
+            secure_delete: true,
+        }
+    }
+}
+
+#[derive(Debug)]
+struct DatabaseCustomizer {
+    config: DatabaseCustomizerConfig,
+}
+
+impl DatabaseCustomizer {
+    fn new(config: DatabaseCustomizerConfig) -> Self
+    where
+        Self: Sized,
+    {
+        Self { config }
+    }
+}
+
+impl CustomizeConnection<SqliteConnection, crate::Error> for DatabaseCustomizer {
+    fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<()> {
+        conn.pragma_set_busy_timeout(self.config.busy_timeout)?;
+        if self.config.journal_mode != SQLiteJournalMode::WAL {
+            conn.pragma_set_journal_mode(self.config.journal_mode, None)?;
+        }
+        conn.pragma_set_synchronous(self.config.synchronous, None)?;
+
+        Ok(())
+    }
+}

+ 186 - 0
rust-lib/flowy-sqlite/src/pragma.rs

@@ -0,0 +1,186 @@
+use crate::errors::{Error, Result};
+use diesel::{
+    expression::SqlLiteral,
+    query_dsl::load_dsl::LoadQuery,
+    sql_types::{Integer, Text},
+    SqliteConnection,
+};
+
+use crate::conn_ext::ConnectionExtension;
+use std::{
+    convert::{TryFrom, TryInto},
+    fmt,
+    str::FromStr,
+};
+
+pub trait PragmaExtension: ConnectionExtension {
+    fn pragma<D: std::fmt::Display>(&self, key: &str, val: D, schema: Option<&str>) -> Result<()> {
+        let query = match schema {
+            Some(schema) => format!("PRAGMA {}.{} = '{}'", schema, key, val),
+            None => format!("PRAGMA {} = '{}'", key, val),
+        };
+        log::trace!("SQLITE {}", query);
+        self.exec(&query)?;
+        Ok(())
+    }
+
+    fn pragma_ret<ST, T, D: std::fmt::Display>(
+        &self,
+        key: &str,
+        val: D,
+        schema: Option<&str>,
+    ) -> Result<T>
+    where
+        SqlLiteral<ST>: LoadQuery<SqliteConnection, T>,
+    {
+        let query = match schema {
+            Some(schema) => format!("PRAGMA {}.{} = '{}'", schema, key, val),
+            None => format!("PRAGMA {} = '{}'", key, val),
+        };
+        log::trace!("SQLITE {}", query);
+        Ok(self.query::<ST, T>(&query)?)
+    }
+
+    fn pragma_get<ST, T>(&self, key: &str, schema: Option<&str>) -> Result<T>
+    where
+        SqlLiteral<ST>: LoadQuery<SqliteConnection, T>,
+    {
+        let query = match schema {
+            Some(schema) => format!("PRAGMA {}.{}", schema, key),
+            None => format!("PRAGMA {}", key),
+        };
+        log::trace!("SQLITE {}", query);
+        Ok(self.query::<ST, T>(&query)?)
+    }
+
+    fn pragma_set_busy_timeout(&self, timeout_ms: i32) -> Result<i32> {
+        self.pragma_ret::<Integer, i32, i32>("busy_timeout", timeout_ms, None)
+    }
+
+    fn pragma_get_busy_timeout(&self) -> Result<i32> {
+        self.pragma_get::<Integer, i32>("busy_timeout", None)
+    }
+
+    fn pragma_set_journal_mode(
+        &self,
+        mode: SQLiteJournalMode,
+        schema: Option<&str>,
+    ) -> Result<i32> {
+        self.pragma_ret::<Integer, i32, SQLiteJournalMode>("journal_mode", mode, schema)
+    }
+
+    fn pragma_get_journal_mode(&self, schema: Option<&str>) -> Result<SQLiteJournalMode> {
+        Ok(self
+            .pragma_get::<Text, String>("journal_mode", schema)?
+            .parse()?)
+    }
+
+    fn pragma_set_synchronous(
+        &self,
+        synchronous: SQLiteSynchronous,
+        schema: Option<&str>,
+    ) -> Result<()> {
+        self.pragma("synchronous", synchronous as u8, schema)
+    }
+
+    fn pragma_get_synchronous(&self, schema: Option<&str>) -> Result<SQLiteSynchronous> {
+        Ok(self
+            .pragma_get::<Integer, i32>("synchronous", schema)?
+            .try_into()?)
+    }
+}
+impl PragmaExtension for SqliteConnection {}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum SQLiteJournalMode {
+    DELETE,
+    TRUNCATE,
+    PERSIST,
+    MEMORY,
+    WAL,
+    OFF,
+}
+
+impl fmt::Display for SQLiteJournalMode {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Self::DELETE => "DELETE",
+                Self::TRUNCATE => "TRUNCATE",
+                Self::PERSIST => "PERSIST",
+                Self::MEMORY => "MEMORY",
+                Self::WAL => "WAL",
+                Self::OFF => "OFF",
+            }
+        )
+    }
+}
+
+impl FromStr for SQLiteJournalMode {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        match s.to_uppercase().as_ref() {
+            "DELETE" => Ok(Self::DELETE),
+            "TRUNCATE" => Ok(Self::TRUNCATE),
+            "PERSIST" => Ok(Self::PERSIST),
+            "MEMORY" => Ok(Self::MEMORY),
+            "WAL" => Ok(Self::WAL),
+            "OFF" => Ok(Self::OFF),
+            _ => Err(format!("Unknown value {} for JournalMode", s).into()),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum SQLiteSynchronous {
+    EXTRA  = 3,
+    FULL   = 2,
+    NORMAL = 1,
+    OFF    = 0,
+}
+
+impl fmt::Display for SQLiteSynchronous {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Self::OFF => "OFF",
+                Self::NORMAL => "NORMAL",
+                Self::FULL => "FULL",
+                Self::EXTRA => "EXTRA",
+            }
+        )
+    }
+}
+
+impl TryFrom<i32> for SQLiteSynchronous {
+    type Error = Error;
+
+    fn try_from(v: i32) -> Result<Self> {
+        match v {
+            0 => Ok(Self::OFF),
+            1 => Ok(Self::NORMAL),
+            2 => Ok(Self::FULL),
+            3 => Ok(Self::EXTRA),
+            _ => Err(format!("Unknown value {} for Synchronous", v).into()),
+        }
+    }
+}
+
+impl FromStr for SQLiteSynchronous {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        match s.to_uppercase().as_ref() {
+            "0" | "OFF" => Ok(Self::OFF),
+            "1" | "NORMAL" => Ok(Self::NORMAL),
+            "2" | "FULL" => Ok(Self::FULL),
+            "3" | "EXTRA" => Ok(Self::EXTRA),
+            _ => Err(format!("Unknown value {} for Synchronous", s).into()),
+        }
+    }
+}

+ 14 - 3
rust-lib/flowy-test/src/builder.rs

@@ -20,18 +20,23 @@ impl WorkspaceTestBuilder {
             tester: Box::new(FixedUserTester::<WorkspaceError>::new()),
             user_detail: None,
         };
-        builder.login()
+
+        builder.login_if_need()
     }
 }
 
 pub type UserTestBuilder = TestBuilder<RandomUserTester<UserError>>;
 impl UserTestBuilder {
     pub fn new() -> Self {
-        Self {
+        let builder = Self {
             tester: Box::new(RandomUserTester::<UserError>::new()),
             user_detail: None,
-        }
+        };
+
+        builder
     }
+
+    pub fn reset(mut self) -> Self { self.logout().login() }
 }
 
 pub struct TestBuilder<T: TesterTrait> {
@@ -49,6 +54,12 @@ where
         self
     }
 
+    pub fn login_if_need(mut self) -> Self {
+        let user_detail = self.tester.login_if_need();
+        self.user_detail = Some(user_detail);
+        self
+    }
+
     pub fn logout(self) -> Self {
         self.tester.logout();
         self

+ 12 - 2
rust-lib/flowy-test/src/tester.rs

@@ -6,7 +6,7 @@ use flowy_dispatch::prelude::*;
 pub use flowy_sdk::*;
 use flowy_user::{
     errors::UserError,
-    event::UserEvent::{SignIn, SignOut},
+    event::UserEvent::{GetStatus, SignIn, SignOut},
     prelude::*,
 };
 use std::{
@@ -99,7 +99,6 @@ pub trait TesterTrait {
 
     fn login(&self) -> UserDetail {
         init_test_sdk(self.context().server.clone());
-        self.logout();
         let payload = SignInRequest {
             email: self.context().user_email.clone(),
             password: valid_password(),
@@ -116,6 +115,17 @@ pub trait TesterTrait {
         user_detail
     }
 
+    fn login_if_need(&self) -> UserDetail {
+        init_test_sdk(self.context().server.clone());
+        match EventDispatch::sync_send(ModuleRequest::new(GetStatus))
+            .parse::<UserDetail, UserError>()
+            .unwrap()
+        {
+            Ok(user_detail) => user_detail,
+            Err(e) => self.login(),
+        }
+    }
+
     fn logout(&self) {
         init_test_sdk(self.context().server.clone());
         let _ = EventDispatch::sync_send(ModuleRequest::new(SignOut));

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

@@ -26,15 +26,15 @@ pub enum UserErrorCode {
     #[display(fmt = "Unknown")]
     Unknown              = 0,
     #[display(fmt = "Database init failed")]
-    DatabaseInitFailed   = 1,
+    UserDatabaseInitFailed = 1,
     #[display(fmt = "Get database write lock failed")]
-    DatabaseWriteLocked  = 2,
+    UserDatabaseWriteLocked = 2,
     #[display(fmt = "Get database read lock failed")]
-    DatabaseReadLocked   = 3,
+    UserDatabaseReadLocked = 3,
     #[display(fmt = "Opening database is not belonging to the current user")]
-    DatabaseUserDidNotMatch = 4,
+    UserDatabaseDidNotMatch = 4,
     #[display(fmt = "Database internal error")]
-    DatabaseInternalError = 5,
+    UserDatabaseInternalError = 5,
 
     #[display(fmt = "User not login yet")]
     UserNotLoginYet      = 10,
@@ -61,7 +61,7 @@ impl std::default::Default for UserErrorCode {
 
 impl std::convert::From<flowy_database::result::Error> for UserError {
     fn from(error: flowy_database::result::Error) -> Self {
-        ErrorBuilder::new(UserErrorCode::DatabaseInternalError)
+        ErrorBuilder::new(UserErrorCode::UserDatabaseInternalError)
             .error(error)
             .build()
     }
@@ -69,7 +69,7 @@ impl std::convert::From<flowy_database::result::Error> for UserError {
 
 impl std::convert::From<flowy_sqlite::Error> for UserError {
     fn from(error: flowy_sqlite::Error) -> Self {
-        ErrorBuilder::new(UserErrorCode::DatabaseInternalError)
+        ErrorBuilder::new(UserErrorCode::UserDatabaseInternalError)
             .error(error)
             .build()
     }

+ 60 - 60
rust-lib/flowy-user/src/protobuf/model/errors.rs

@@ -216,11 +216,11 @@ impl ::protobuf::reflect::ProtobufValue for UserError {
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum UserErrorCode {
     Unknown = 0,
-    DatabaseInitFailed = 1,
-    DatabaseWriteLocked = 2,
-    DatabaseReadLocked = 3,
-    DatabaseUserDidNotMatch = 4,
-    DatabaseInternalError = 5,
+    UserDatabaseInitFailed = 1,
+    UserDatabaseWriteLocked = 2,
+    UserDatabaseReadLocked = 3,
+    UserDatabaseDidNotMatch = 4,
+    UserDatabaseInternalError = 5,
     UserNotLoginYet = 10,
     ReadCurrentIdFailed = 11,
     WriteCurrentIdFailed = 12,
@@ -239,11 +239,11 @@ impl ::protobuf::ProtobufEnum for UserErrorCode {
     fn from_i32(value: i32) -> ::std::option::Option<UserErrorCode> {
         match value {
             0 => ::std::option::Option::Some(UserErrorCode::Unknown),
-            1 => ::std::option::Option::Some(UserErrorCode::DatabaseInitFailed),
-            2 => ::std::option::Option::Some(UserErrorCode::DatabaseWriteLocked),
-            3 => ::std::option::Option::Some(UserErrorCode::DatabaseReadLocked),
-            4 => ::std::option::Option::Some(UserErrorCode::DatabaseUserDidNotMatch),
-            5 => ::std::option::Option::Some(UserErrorCode::DatabaseInternalError),
+            1 => ::std::option::Option::Some(UserErrorCode::UserDatabaseInitFailed),
+            2 => ::std::option::Option::Some(UserErrorCode::UserDatabaseWriteLocked),
+            3 => ::std::option::Option::Some(UserErrorCode::UserDatabaseReadLocked),
+            4 => ::std::option::Option::Some(UserErrorCode::UserDatabaseDidNotMatch),
+            5 => ::std::option::Option::Some(UserErrorCode::UserDatabaseInternalError),
             10 => ::std::option::Option::Some(UserErrorCode::UserNotLoginYet),
             11 => ::std::option::Option::Some(UserErrorCode::ReadCurrentIdFailed),
             12 => ::std::option::Option::Some(UserErrorCode::WriteCurrentIdFailed),
@@ -259,11 +259,11 @@ impl ::protobuf::ProtobufEnum for UserErrorCode {
     fn values() -> &'static [Self] {
         static values: &'static [UserErrorCode] = &[
             UserErrorCode::Unknown,
-            UserErrorCode::DatabaseInitFailed,
-            UserErrorCode::DatabaseWriteLocked,
-            UserErrorCode::DatabaseReadLocked,
-            UserErrorCode::DatabaseUserDidNotMatch,
-            UserErrorCode::DatabaseInternalError,
+            UserErrorCode::UserDatabaseInitFailed,
+            UserErrorCode::UserDatabaseWriteLocked,
+            UserErrorCode::UserDatabaseReadLocked,
+            UserErrorCode::UserDatabaseDidNotMatch,
+            UserErrorCode::UserDatabaseInternalError,
             UserErrorCode::UserNotLoginYet,
             UserErrorCode::ReadCurrentIdFailed,
             UserErrorCode::WriteCurrentIdFailed,
@@ -302,51 +302,51 @@ impl ::protobuf::reflect::ProtobufValue for UserErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"A\n\tUserError\x12\"\n\x04code\x18\x01\x20\x01(\x0e2\
     \x0e.UserErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03msg*\
-    \xce\x02\n\rUserErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x16\n\x12Databa\
-    seInitFailed\x10\x01\x12\x17\n\x13DatabaseWriteLocked\x10\x02\x12\x16\n\
-    \x12DatabaseReadLocked\x10\x03\x12\x1b\n\x17DatabaseUserDidNotMatch\x10\
-    \x04\x12\x19\n\x15DatabaseInternalError\x10\x05\x12\x13\n\x0fUserNotLogi\
-    nYet\x10\n\x12\x17\n\x13ReadCurrentIdFailed\x10\x0b\x12\x18\n\x14WriteCu\
-    rrentIdFailed\x10\x0c\x12\x10\n\x0cEmailInvalid\x10\x14\x12\x13\n\x0fPas\
-    swordInvalid\x10\x15\x12\x13\n\x0fUserNameInvalid\x10\x16\x12\x18\n\x14U\
-    serWorkspaceInvalid\x10\x17\x12\x11\n\rUserIdInvalid\x10\x18J\xee\x05\n\
-    \x06\x12\x04\0\0\x15\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\x11\n\x0b\n\
-    \x04\x04\0\x02\0\x12\x03\x03\x04\x1b\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\
-    \x03\x04\x11\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x12\x16\n\x0c\n\x05\
-    \x04\0\x02\0\x03\x12\x03\x03\x19\x1a\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
-    \x04\x04\x13\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\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
-    \x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x15\x01\n\n\n\x03\x05\0\
-    \x01\x12\x03\x06\x05\x12\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\
-    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
-    \x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x1b\n\
-    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x16\n\x0c\n\x05\x05\0\x02\
-    \x01\x02\x12\x03\x08\x19\x1a\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x1c\
-    \n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x17\n\x0c\n\x05\x05\0\x02\
-    \x02\x02\x12\x03\t\x1a\x1b\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\x04\x1b\n\
-    \x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x16\n\x0c\n\x05\x05\0\x02\x03\
-    \x02\x12\x03\n\x19\x1a\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\x04\x20\n\
-    \x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x1b\n\x0c\n\x05\x05\0\x02\
-    \x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x0c\x04\
-    \x1e\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x19\n\x0c\n\x05\x05\0\
-    \x02\x05\x02\x12\x03\x0c\x1c\x1d\n\x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\
-    \x19\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x13\n\x0c\n\x05\x05\0\
-    \x02\x06\x02\x12\x03\r\x16\x18\n\x0b\n\x04\x05\0\x02\x07\x12\x03\x0e\x04\
-    \x1d\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x17\n\x0c\n\x05\x05\0\
-    \x02\x07\x02\x12\x03\x0e\x1a\x1c\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\
-    \x04\x1e\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\x18\n\x0c\n\x05\
-    \x05\0\x02\x08\x02\x12\x03\x0f\x1b\x1d\n\x0b\n\x04\x05\0\x02\t\x12\x03\
-    \x10\x04\x16\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\x10\n\x0c\n\x05\
-    \x05\0\x02\t\x02\x12\x03\x10\x13\x15\n\x0b\n\x04\x05\0\x02\n\x12\x03\x11\
-    \x04\x19\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\x13\n\x0c\n\x05\x05\
-    \0\x02\n\x02\x12\x03\x11\x16\x18\n\x0b\n\x04\x05\0\x02\x0b\x12\x03\x12\
-    \x04\x19\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\x04\x13\n\x0c\n\x05\
-    \x05\0\x02\x0b\x02\x12\x03\x12\x16\x18\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\
-    \x13\x04\x1e\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x13\x04\x18\n\x0c\n\
-    \x05\x05\0\x02\x0c\x02\x12\x03\x13\x1b\x1d\n\x0b\n\x04\x05\0\x02\r\x12\
-    \x03\x14\x04\x17\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x14\x04\x11\n\x0c\n\
-    \x05\x05\0\x02\r\x02\x12\x03\x14\x14\x16b\x06proto3\
+    \xde\x02\n\rUserErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16UserDa\
+    tabaseInitFailed\x10\x01\x12\x1b\n\x17UserDatabaseWriteLocked\x10\x02\
+    \x12\x1a\n\x16UserDatabaseReadLocked\x10\x03\x12\x1b\n\x17UserDatabaseDi\
+    dNotMatch\x10\x04\x12\x1d\n\x19UserDatabaseInternalError\x10\x05\x12\x13\
+    \n\x0fUserNotLoginYet\x10\n\x12\x17\n\x13ReadCurrentIdFailed\x10\x0b\x12\
+    \x18\n\x14WriteCurrentIdFailed\x10\x0c\x12\x10\n\x0cEmailInvalid\x10\x14\
+    \x12\x13\n\x0fPasswordInvalid\x10\x15\x12\x13\n\x0fUserNameInvalid\x10\
+    \x16\x12\x18\n\x14UserWorkspaceInvalid\x10\x17\x12\x11\n\rUserIdInvalid\
+    \x10\x18J\xee\x05\n\x06\x12\x04\0\0\x15\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\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x1b\n\x0c\n\x05\x04\
+    \0\x02\0\x06\x12\x03\x03\x04\x11\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\
+    \x12\x16\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x19\x1a\n\x0b\n\x04\x04\
+    \0\x02\x01\x12\x03\x04\x04\x13\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\x0e\n\x0c\n\x05\x04\
+    \0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x15\x01\
+    \n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x12\n\x0b\n\x04\x05\0\x02\0\x12\x03\
+    \x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\
+    \x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\
+    \x08\x04\x1f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x1a\n\x0c\n\
+    \x05\x05\0\x02\x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\x04\x05\0\x02\x02\x12\
+    \x03\t\x04\x20\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x1b\n\x0c\n\
+    \x05\x05\0\x02\x02\x02\x12\x03\t\x1e\x1f\n\x0b\n\x04\x05\0\x02\x03\x12\
+    \x03\n\x04\x1f\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x1a\n\x0c\n\
+    \x05\x05\0\x02\x03\x02\x12\x03\n\x1d\x1e\n\x0b\n\x04\x05\0\x02\x04\x12\
+    \x03\x0b\x04\x20\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x1b\n\x0c\
+    \n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\x05\0\x02\x05\
+    \x12\x03\x0c\x04\"\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x20!\n\x0b\n\x04\x05\0\x02\x06\
+    \x12\x03\r\x04\x19\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x13\n\x0c\
+    \n\x05\x05\0\x02\x06\x02\x12\x03\r\x16\x18\n\x0b\n\x04\x05\0\x02\x07\x12\
+    \x03\x0e\x04\x1d\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x17\n\x0c\
+    \n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x1a\x1c\n\x0b\n\x04\x05\0\x02\x08\
+    \x12\x03\x0f\x04\x1e\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\x18\n\
+    \x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x1b\x1d\n\x0b\n\x04\x05\0\x02\t\
+    \x12\x03\x10\x04\x16\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x13\x15\n\x0b\n\x04\x05\0\x02\n\
+    \x12\x03\x11\x04\x19\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\x13\n\
+    \x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x16\x18\n\x0b\n\x04\x05\0\x02\x0b\
+    \x12\x03\x12\x04\x19\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\x04\x13\n\
+    \x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x16\x18\n\x0b\n\x04\x05\0\x02\
+    \x0c\x12\x03\x13\x04\x1e\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x13\x04\
+    \x18\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\x1b\x1d\n\x0b\n\x04\x05\0\
+    \x02\r\x12\x03\x14\x04\x17\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x14\x04\
+    \x11\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x14\x16b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 5 - 5
rust-lib/flowy-user/src/protobuf/proto/errors.proto

@@ -6,11 +6,11 @@ message UserError {
 }
 enum UserErrorCode {
     Unknown = 0;
-    DatabaseInitFailed = 1;
-    DatabaseWriteLocked = 2;
-    DatabaseReadLocked = 3;
-    DatabaseUserDidNotMatch = 4;
-    DatabaseInternalError = 5;
+    UserDatabaseInitFailed = 1;
+    UserDatabaseWriteLocked = 2;
+    UserDatabaseReadLocked = 3;
+    UserDatabaseDidNotMatch = 4;
+    UserDatabaseInternalError = 5;
     UserNotLoginYet = 10;
     ReadCurrentIdFailed = 11;
     WriteCurrentIdFailed = 12;

+ 7 - 6
rust-lib/flowy-user/src/services/user_session/database.rs

@@ -22,20 +22,21 @@ impl UserDB {
 
     fn open_user_db(&self, user_id: &str) -> Result<(), UserError> {
         if user_id.is_empty() {
-            return Err(ErrorBuilder::new(UserErrorCode::DatabaseInitFailed)
+            return Err(ErrorBuilder::new(UserErrorCode::UserDatabaseInitFailed)
                 .msg("user id is empty")
                 .build());
         }
 
         let dir = format!("{}/{}", self.db_dir, user_id);
         let db = flowy_database::init(&dir).map_err(|e| {
-            ErrorBuilder::new(UserErrorCode::DatabaseInitFailed)
+            log::error!("flowy_database::init failed, {:?}", e);
+            ErrorBuilder::new(UserErrorCode::UserDatabaseInitFailed)
                 .error(e)
                 .build()
         })?;
 
         let mut db_map = DB_MAP.write().map_err(|e| {
-            ErrorBuilder::new(UserErrorCode::DatabaseWriteLocked)
+            ErrorBuilder::new(UserErrorCode::UserDatabaseWriteLocked)
                 .error(e)
                 .build()
         })?;
@@ -46,7 +47,7 @@ impl UserDB {
 
     pub(crate) fn close_user_db(&self, user_id: &str) -> Result<(), UserError> {
         let mut db_map = DB_MAP.write().map_err(|e| {
-            ErrorBuilder::new(UserErrorCode::DatabaseWriteLocked)
+            ErrorBuilder::new(UserErrorCode::UserDatabaseWriteLocked)
                 .msg(format!("Close user db failed. {:?}", e))
                 .build()
         })?;
@@ -62,13 +63,13 @@ impl UserDB {
         }
 
         let db_map = DB_MAP.read().map_err(|e| {
-            ErrorBuilder::new(UserErrorCode::DatabaseReadLocked)
+            ErrorBuilder::new(UserErrorCode::UserDatabaseReadLocked)
                 .error(e)
                 .build()
         })?;
 
         match db_map.get(user_id) {
-            None => Err(ErrorBuilder::new(UserErrorCode::DatabaseInitFailed)
+            None => Err(ErrorBuilder::new(UserErrorCode::UserDatabaseInitFailed)
                 .msg("Get connection failed. The database is not initialization")
                 .build()),
             Some(database) => Ok(database.get_connection()?),

+ 11 - 13
rust-lib/flowy-user/src/services/user_session/user_session.rs

@@ -104,11 +104,9 @@ impl UserSession {
 
     pub fn user_detail(&self) -> Result<UserDetail, UserError> {
         let user_id = self.get_user_id()?;
-        let conn = self.get_db_connection()?;
-
         let user = dsl::user_table
             .filter(user_table::id.eq(&user_id))
-            .first::<User>(&*conn)?;
+            .first::<User>(&*(self.get_db_connection()?))?;
 
         match self.server.get_user_info(&user_id) {
             Ok(_user_detail) => {
@@ -137,14 +135,15 @@ impl UserSession {
     }
 
     pub fn get_user_id(&self) -> Result<String, UserError> {
-        let read_guard = self.user_id.read().map_err(|e| {
-            ErrorBuilder::new(UserErrorCode::ReadCurrentIdFailed)
-                .error(e)
-                .build()
-        })?;
+        let mut user_id = {
+            let read_guard = self.user_id.read().map_err(|e| {
+                ErrorBuilder::new(UserErrorCode::ReadCurrentIdFailed)
+                    .error(e)
+                    .build()
+            })?;
 
-        let mut user_id = (*read_guard).clone();
-        drop(read_guard);
+            (*read_guard).clone()
+        };
 
         if user_id.is_none() {
             user_id = KVStore::get_str(USER_ID_CACHE_KEY);
@@ -165,11 +164,10 @@ impl UserSession {
             .unwrap();
 
         let request = ModuleRequest::new(UpdateUser).payload(payload);
-        let _user_detail = EventDispatch::async_send(request)
+        let _ = EventDispatch::async_send(request)
             .await
             .parse::<UserDetail, UserError>()
-            .unwrap()
-            .unwrap();
+            .unwrap()?;
         Ok(())
     }
 }

+ 0 - 230
rust-lib/flowy-user/tests/event/sign_in_test.rs

@@ -60,233 +60,3 @@ fn sign_in_with_invalid_password() {
         );
     }
 }
-
-#[test]
-#[serial]
-fn sign_up_success() {
-    let _ = UserTestBuilder::new().event(SignOut).sync_send();
-    let request = SignUpRequest {
-        email: random_valid_email(),
-        name: valid_name(),
-        password: valid_password(),
-    };
-
-    let _response = UserTestBuilder::new()
-        .logout()
-        .event(SignUp)
-        .request(request)
-        .sync_send();
-}
-
-#[test]
-#[serial]
-fn sign_up_with_invalid_email() {
-    for email in invalid_email_test_case() {
-        let request = SignUpRequest {
-            email: email.to_string(),
-            name: valid_name(),
-            password: valid_password(),
-        };
-
-        assert_eq!(
-            UserTestBuilder::new()
-                .event(SignUp)
-                .request(request)
-                .sync_send()
-                .error()
-                .code,
-            UserErrorCode::EmailInvalid
-        );
-    }
-}
-#[test]
-#[serial]
-fn sign_up_with_invalid_password() {
-    for password in invalid_password_test_case() {
-        let request = SignUpRequest {
-            email: random_valid_email(),
-            name: valid_name(),
-            password,
-        };
-
-        assert_eq!(
-            UserTestBuilder::new()
-                .event(SignUp)
-                .request(request)
-                .sync_send()
-                .error()
-                .code,
-            UserErrorCode::PasswordInvalid
-        );
-    }
-}
-
-#[test]
-#[should_panic]
-#[serial]
-fn user_status_get_failed_before_login() {
-    let _ = UserTestBuilder::new()
-        .logout()
-        .event(GetStatus)
-        .sync_send()
-        .parse::<UserDetail>();
-}
-
-#[test]
-#[serial]
-fn user_status_get_success_after_login() {
-    let request = SignInRequest {
-        email: random_valid_email(),
-        password: valid_password(),
-    };
-
-    let response = UserTestBuilder::new()
-        .logout()
-        .event(SignIn)
-        .request(request)
-        .sync_send()
-        .parse::<UserDetail>();
-    dbg!(&response);
-
-    let _ = UserTestBuilder::new()
-        .event(GetStatus)
-        .sync_send()
-        .parse::<UserDetail>();
-}
-
-#[test]
-#[serial]
-fn user_update_with_name() {
-    let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-    let new_name = "hello_world".to_owned();
-    let request = UpdateUserRequest {
-        id: user_detail.id.clone(),
-        name: Some(new_name.clone()),
-        email: None,
-        workspace: None,
-        password: None,
-    };
-
-    let user_detail = UserTestBuilder::new()
-        .event(UpdateUser)
-        .request(request)
-        .sync_send()
-        .parse::<UserDetail>();
-
-    assert_eq!(user_detail.name, new_name,);
-}
-
-#[test]
-#[serial]
-fn user_update_with_email() {
-    let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-    let new_email = "[email protected]".to_owned();
-    let request = UpdateUserRequest {
-        id: user_detail.id.clone(),
-        name: None,
-        email: Some(new_email.clone()),
-        workspace: None,
-        password: None,
-    };
-
-    let user_detail = UserTestBuilder::new()
-        .event(UpdateUser)
-        .request(request)
-        .sync_send()
-        .parse::<UserDetail>();
-
-    assert_eq!(user_detail.email, new_email,);
-}
-
-#[test]
-#[serial]
-fn user_update_with_password() {
-    let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-    let new_password = "H123world!".to_owned();
-    let request = UpdateUserRequest {
-        id: user_detail.id.clone(),
-        name: None,
-        email: None,
-        workspace: None,
-        password: Some(new_password.clone()),
-    };
-
-    let _ = UserTestBuilder::new()
-        .event(UpdateUser)
-        .request(request)
-        .sync_send()
-        .assert_success();
-}
-
-#[test]
-#[serial]
-fn user_update_with_invalid_email() {
-    let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-    for email in invalid_email_test_case() {
-        let request = UpdateUserRequest {
-            id: user_detail.id.clone(),
-            name: None,
-            email: Some(email),
-            workspace: None,
-            password: None,
-        };
-
-        assert_eq!(
-            UserTestBuilder::new()
-                .event(UpdateUser)
-                .request(request)
-                .sync_send()
-                .error()
-                .code,
-            UserErrorCode::EmailInvalid
-        );
-    }
-}
-
-#[test]
-#[serial]
-fn user_update_with_invalid_password() {
-    let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-    for password in invalid_password_test_case() {
-        let request = UpdateUserRequest {
-            id: user_detail.id.clone(),
-            name: None,
-            email: None,
-            workspace: None,
-            password: Some(password),
-        };
-
-        assert_eq!(
-            UserTestBuilder::new()
-                .event(UpdateUser)
-                .request(request)
-                .sync_send()
-                .error()
-                .code,
-            UserErrorCode::PasswordInvalid
-        );
-    }
-}
-
-#[test]
-#[serial]
-fn user_update_with_invalid_name() {
-    let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-    let request = UpdateUserRequest {
-        id: user_detail.id.clone(),
-        name: Some("".to_string()),
-        email: None,
-        workspace: None,
-        password: None,
-    };
-
-    assert_eq!(
-        UserTestBuilder::new()
-            .event(UpdateUser)
-            .request(request)
-            .sync_send()
-            .error()
-            .code,
-        UserErrorCode::UserNameInvalid
-    );
-}

+ 65 - 65
rust-lib/flowy-user/tests/event/sign_up_test.rs

@@ -1,65 +1,65 @@
-// use crate::helper::*;
-// use flowy_user::{errors::*, event::UserEvent::*, prelude::*};
-// use serial_test::*;
-//
-// #[test]
-// #[serial]
-// fn sign_up_success() {
-//     let _ = UserTestBuilder::new().event(SignOut).sync_send();
-//     let request = SignUpRequest {
-//         email: random_valid_email(),
-//         name: valid_name(),
-//         password: valid_password(),
-//     };
-//
-//     let _response = UserTestBuilder::new()
-//         .logout()
-//         .event(SignUp)
-//         .request(request)
-//         .sync_send();
-//     // .parse::<SignUpResponse>();
-//     // dbg!(&response);
-// }
-//
-// #[test]
-// #[serial]
-// fn sign_up_with_invalid_email() {
-//     for email in invalid_email_test_case() {
-//         let request = SignUpRequest {
-//             email: email.to_string(),
-//             name: valid_name(),
-//             password: valid_password(),
-//         };
-//
-//         assert_eq!(
-//             UserTestBuilder::new()
-//                 .event(SignUp)
-//                 .request(request)
-//                 .sync_send()
-//                 .error()
-//                 .code,
-//             UserErrorCode::EmailInvalid
-//         );
-//     }
-// }
-// #[test]
-// #[serial]
-// fn sign_up_with_invalid_password() {
-//     for password in invalid_password_test_case() {
-//         let request = SignUpRequest {
-//             email: random_valid_email(),
-//             name: valid_name(),
-//             password,
-//         };
-//
-//         assert_eq!(
-//             UserTestBuilder::new()
-//                 .event(SignUp)
-//                 .request(request)
-//                 .sync_send()
-//                 .error()
-//                 .code,
-//             UserErrorCode::PasswordInvalid
-//         );
-//     }
-// }
+use crate::helper::*;
+use flowy_user::{errors::*, event::UserEvent::*, prelude::*};
+use serial_test::*;
+
+#[test]
+#[serial]
+fn sign_up_success() {
+    let _ = UserTestBuilder::new().event(SignOut).sync_send();
+    let request = SignUpRequest {
+        email: random_valid_email(),
+        name: valid_name(),
+        password: valid_password(),
+    };
+
+    let _response = UserTestBuilder::new()
+        .logout()
+        .event(SignUp)
+        .request(request)
+        .sync_send();
+    // .parse::<SignUpResponse>();
+    // dbg!(&response);
+}
+
+#[test]
+#[serial]
+fn sign_up_with_invalid_email() {
+    for email in invalid_email_test_case() {
+        let request = SignUpRequest {
+            email: email.to_string(),
+            name: valid_name(),
+            password: valid_password(),
+        };
+
+        assert_eq!(
+            UserTestBuilder::new()
+                .event(SignUp)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
+            UserErrorCode::EmailInvalid
+        );
+    }
+}
+#[test]
+#[serial]
+fn sign_up_with_invalid_password() {
+    for password in invalid_password_test_case() {
+        let request = SignUpRequest {
+            email: random_valid_email(),
+            name: valid_name(),
+            password,
+        };
+
+        assert_eq!(
+            UserTestBuilder::new()
+                .event(SignUp)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
+            UserErrorCode::PasswordInvalid
+        );
+    }
+}

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

@@ -1,36 +1,36 @@
-// use crate::helper::*;
-// use flowy_user::{event::UserEvent::*, prelude::*};
-// use serial_test::*;
-//
-// #[test]
-// #[should_panic]
-// #[serial]
-// fn user_status_get_failed_before_login() {
-//     let _ = UserTestBuilder::new()
-//         .logout()
-//         .event(GetStatus)
-//         .sync_send()
-//         .parse::<UserDetail>();
-// }
-//
-// #[test]
-// #[serial]
-// fn user_status_get_success_after_login() {
-//     let request = SignInRequest {
-//         email: random_valid_email(),
-//         password: valid_password(),
-//     };
-//
-//     let response = UserTestBuilder::new()
-//         .logout()
-//         .event(SignIn)
-//         .request(request)
-//         .sync_send()
-//         .parse::<UserDetail>();
-//     dbg!(&response);
-//
-//     let _ = UserTestBuilder::new()
-//         .event(GetStatus)
-//         .sync_send()
-//         .parse::<UserDetail>();
-// }
+use crate::helper::*;
+use flowy_user::{event::UserEvent::*, prelude::*};
+use serial_test::*;
+
+#[test]
+#[should_panic]
+#[serial]
+fn user_status_get_failed_before_login() {
+    let _ = UserTestBuilder::new()
+        .logout()
+        .event(GetStatus)
+        .sync_send()
+        .parse::<UserDetail>();
+}
+
+#[test]
+#[serial]
+fn user_status_get_success_after_login() {
+    let request = SignInRequest {
+        email: random_valid_email(),
+        password: valid_password(),
+    };
+
+    let response = UserTestBuilder::new()
+        .logout()
+        .event(SignIn)
+        .request(request)
+        .sync_send()
+        .parse::<UserDetail>();
+    dbg!(&response);
+
+    let _ = UserTestBuilder::new()
+        .event(GetStatus)
+        .sync_send()
+        .parse::<UserDetail>();
+}

+ 140 - 140
rust-lib/flowy-user/tests/event/user_update_test.rs

@@ -1,140 +1,140 @@
-// use crate::helper::*;
-// use flowy_user::{errors::UserErrorCode, event::UserEvent::*, prelude::*};
-// use serial_test::*;
-//
-// #[test]
-// #[serial]
-// fn user_update_with_name() {
-//     let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-//     let new_name = "hello_world".to_owned();
-//     let request = UpdateUserRequest {
-//         id: user_detail.id.clone(),
-//         name: Some(new_name.clone()),
-//         email: None,
-//         workspace: None,
-//         password: None,
-//     };
-//
-//     let user_detail = UserTestBuilder::new()
-//         .event(UpdateUser)
-//         .request(request)
-//         .sync_send()
-//         .parse::<UserDetail>();
-//
-//     assert_eq!(user_detail.name, new_name,);
-// }
-//
-// #[test]
-// #[serial]
-// fn user_update_with_email() {
-//     let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-//     let new_email = "[email protected]".to_owned();
-//     let request = UpdateUserRequest {
-//         id: user_detail.id.clone(),
-//         name: None,
-//         email: Some(new_email.clone()),
-//         workspace: None,
-//         password: None,
-//     };
-//
-//     let user_detail = UserTestBuilder::new()
-//         .event(UpdateUser)
-//         .request(request)
-//         .sync_send()
-//         .parse::<UserDetail>();
-//
-//     assert_eq!(user_detail.email, new_email,);
-// }
-//
-// #[test]
-// #[serial]
-// fn user_update_with_password() {
-//     let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-//     let new_password = "H123world!".to_owned();
-//     let request = UpdateUserRequest {
-//         id: user_detail.id.clone(),
-//         name: None,
-//         email: None,
-//         workspace: None,
-//         password: Some(new_password.clone()),
-//     };
-//
-//     let _ = UserTestBuilder::new()
-//         .event(UpdateUser)
-//         .request(request)
-//         .sync_send()
-//         .assert_success();
-// }
-//
-// #[test]
-// #[serial]
-// fn user_update_with_invalid_email() {
-//     let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-//     for email in invalid_email_test_case() {
-//         let request = UpdateUserRequest {
-//             id: user_detail.id.clone(),
-//             name: None,
-//             email: Some(email),
-//             workspace: None,
-//             password: None,
-//         };
-//
-//         assert_eq!(
-//             UserTestBuilder::new()
-//                 .event(UpdateUser)
-//                 .request(request)
-//                 .sync_send()
-//                 .error()
-//                 .code,
-//             UserErrorCode::EmailInvalid
-//         );
-//     }
-// }
-//
-// #[test]
-// #[serial]
-// fn user_update_with_invalid_password() {
-//     let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-//     for password in invalid_password_test_case() {
-//         let request = UpdateUserRequest {
-//             id: user_detail.id.clone(),
-//             name: None,
-//             email: None,
-//             workspace: None,
-//             password: Some(password),
-//         };
-//
-//         assert_eq!(
-//             UserTestBuilder::new()
-//                 .event(UpdateUser)
-//                 .request(request)
-//                 .sync_send()
-//                 .error()
-//                 .code,
-//             UserErrorCode::PasswordInvalid
-//         );
-//     }
-// }
-//
-// #[test]
-// #[serial]
-// fn user_update_with_invalid_name() {
-//     let user_detail = UserTestBuilder::new().login().user_detail.unwrap();
-//     let request = UpdateUserRequest {
-//         id: user_detail.id.clone(),
-//         name: Some("".to_string()),
-//         email: None,
-//         workspace: None,
-//         password: None,
-//     };
-//
-//     assert_eq!(
-//         UserTestBuilder::new()
-//             .event(UpdateUser)
-//             .request(request)
-//             .sync_send()
-//             .error()
-//             .code,
-//         UserErrorCode::UserNameInvalid
-//     );
-// }
+use crate::helper::*;
+use flowy_user::{errors::UserErrorCode, event::UserEvent::*, prelude::*};
+use serial_test::*;
+
+#[test]
+#[serial]
+fn user_update_with_name() {
+    let user_detail = UserTestBuilder::new().reset().user_detail.unwrap();
+    let new_name = "hello_world".to_owned();
+    let request = UpdateUserRequest {
+        id: user_detail.id.clone(),
+        name: Some(new_name.clone()),
+        email: None,
+        workspace: None,
+        password: None,
+    };
+
+    let user_detail = UserTestBuilder::new()
+        .event(UpdateUser)
+        .request(request)
+        .sync_send()
+        .parse::<UserDetail>();
+
+    assert_eq!(user_detail.name, new_name,);
+}
+
+#[test]
+#[serial]
+fn user_update_with_email() {
+    let user_detail = UserTestBuilder::new().reset().user_detail.unwrap();
+    let new_email = "[email protected]".to_owned();
+    let request = UpdateUserRequest {
+        id: user_detail.id.clone(),
+        name: None,
+        email: Some(new_email.clone()),
+        workspace: None,
+        password: None,
+    };
+
+    let user_detail = UserTestBuilder::new()
+        .event(UpdateUser)
+        .request(request)
+        .sync_send()
+        .parse::<UserDetail>();
+
+    assert_eq!(user_detail.email, new_email,);
+}
+
+#[test]
+#[serial]
+fn user_update_with_password() {
+    let user_detail = UserTestBuilder::new().reset().user_detail.unwrap();
+    let new_password = "H123world!".to_owned();
+    let request = UpdateUserRequest {
+        id: user_detail.id.clone(),
+        name: None,
+        email: None,
+        workspace: None,
+        password: Some(new_password.clone()),
+    };
+
+    let _ = UserTestBuilder::new()
+        .event(UpdateUser)
+        .request(request)
+        .sync_send()
+        .assert_success();
+}
+
+#[test]
+#[serial]
+fn user_update_with_invalid_email() {
+    let user_detail = UserTestBuilder::new().reset().user_detail.unwrap();
+    for email in invalid_email_test_case() {
+        let request = UpdateUserRequest {
+            id: user_detail.id.clone(),
+            name: None,
+            email: Some(email),
+            workspace: None,
+            password: None,
+        };
+
+        assert_eq!(
+            UserTestBuilder::new()
+                .event(UpdateUser)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
+            UserErrorCode::EmailInvalid
+        );
+    }
+}
+
+#[test]
+#[serial]
+fn user_update_with_invalid_password() {
+    let user_detail = UserTestBuilder::new().reset().user_detail.unwrap();
+    for password in invalid_password_test_case() {
+        let request = UpdateUserRequest {
+            id: user_detail.id.clone(),
+            name: None,
+            email: None,
+            workspace: None,
+            password: Some(password),
+        };
+
+        assert_eq!(
+            UserTestBuilder::new()
+                .event(UpdateUser)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
+            UserErrorCode::PasswordInvalid
+        );
+    }
+}
+
+#[test]
+#[serial]
+fn user_update_with_invalid_name() {
+    let user_detail = UserTestBuilder::new().reset().user_detail.unwrap();
+    let request = UpdateUserRequest {
+        id: user_detail.id.clone(),
+        name: Some("".to_string()),
+        email: None,
+        workspace: None,
+        password: None,
+    };
+
+    assert_eq!(
+        UserTestBuilder::new()
+            .event(UpdateUser)
+            .request(request)
+            .sync_send()
+            .error()
+            .code,
+        UserErrorCode::UserNameInvalid
+    );
+}

+ 18 - 4
rust-lib/flowy-workspace/src/entities/workspace/workspace_user_detail.rs

@@ -1,6 +1,20 @@
-pub struct WorkspaceUserQueryRequest {
-    fetch_owner: bool,
-    fetch_all: bool,
+use crate::entities::workspace::WorkspaceDetail;
+use flowy_derive::ProtoBuf;
+
+#[derive(ProtoBuf, Default, Debug)]
+pub struct UserWorkspace {
+    #[pb(index = 1)]
+    pub owner: String,
+
+    #[pb(index = 2)]
+    pub workspace_id: String,
 }
 
-pub struct WorkspaceUserDetail {}
+#[derive(ProtoBuf, Default, Debug)]
+pub struct UserWorkspaceDetail {
+    #[pb(index = 1)]
+    pub owner: String,
+
+    #[pb(index = 2)]
+    pub workspace: WorkspaceDetail,
+}

+ 2 - 10
rust-lib/flowy-workspace/src/errors.rs

@@ -42,7 +42,7 @@ pub enum WorkspaceErrorCode {
     DatabaseConnectionFail = 5,
 
     #[display(fmt = "Database internal error")]
-    DatabaseInternalError = 6,
+    WorkspaceDatabaseError = 6,
 
     #[display(fmt = "User internal error")]
     UserInternalError    = 10,
@@ -57,15 +57,7 @@ impl std::default::Default for WorkspaceErrorCode {
 
 impl std::convert::From<flowy_database::result::Error> for WorkspaceError {
     fn from(error: flowy_database::result::Error) -> Self {
-        ErrorBuilder::new(WorkspaceErrorCode::DatabaseInternalError)
-            .error(error)
-            .build()
-    }
-}
-
-impl std::convert::From<flowy_sqlite::Error> for WorkspaceError {
-    fn from(error: flowy_sqlite::Error) -> Self {
-        ErrorBuilder::new(WorkspaceErrorCode::DatabaseInternalError)
+        ErrorBuilder::new(WorkspaceErrorCode::WorkspaceDatabaseError)
             .error(error)
             .build()
     }

+ 4 - 4
rust-lib/flowy-workspace/src/event.rs

@@ -6,9 +6,9 @@ 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 workspace user")]
-    #[event(output = "UserDetail")]
-    GetWorkspaceUserDetail = 100,
+    #[display(fmt = "Get user's workspace detail")]
+    #[event(output = "UserWorkspaceDetail")]
+    GetWorkspaceDetail = 1,
 }

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

@@ -1,5 +1,11 @@
 use crate::{
-    entities::workspace::{CreateWorkspaceParams, CreateWorkspaceRequest, WorkspaceDetail},
+    entities::workspace::{
+        CreateWorkspaceParams,
+        CreateWorkspaceRequest,
+        UserWorkspace,
+        UserWorkspaceDetail,
+        WorkspaceDetail,
+    },
     errors::WorkspaceError,
     services::WorkspaceController,
 };
@@ -16,12 +22,9 @@ pub async fn create_workspace(
     response_ok(detail)
 }
 
-pub async fn workspace_user(
-    data: Data<CreateWorkspaceRequest>,
+pub async fn get_workspace_detail(
     controller: ModuleData<Arc<WorkspaceController>>,
-) -> ResponseResult<WorkspaceDetail, WorkspaceError> {
-    let controller = controller.get_ref().clone();
-    let params: CreateWorkspaceParams = data.into_inner().try_into()?;
-    let detail = controller.save_workspace(params).await?;
-    response_ok(detail)
+) -> ResponseResult<UserWorkspaceDetail, WorkspaceError> {
+    let user_workspace = controller.get_user_workspace_detail().await?;
+    response_ok(user_workspace)
 }

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

@@ -7,12 +7,12 @@ use crate::{
 };
 use flowy_database::DBConnection;
 
-use crate::handlers::*;
+use crate::{entities::workspace::UserWorkspace, handlers::*};
 use std::sync::Arc;
 
 pub trait WorkspaceUser: Send + Sync {
-    fn set_workspace(&self, id: &str) -> DispatchFuture<Result<(), WorkspaceError>>;
-    fn get_workspace(&self) -> Result<String, WorkspaceError>;
+    fn set_cur_workspace_id(&self, id: &str) -> DispatchFuture<Result<(), WorkspaceError>>;
+    fn get_cur_workspace(&self) -> DispatchFuture<Result<UserWorkspace, WorkspaceError>>;
     fn db_connection(&self) -> Result<DBConnection, WorkspaceError>;
 }
 
@@ -25,5 +25,5 @@ pub fn create(user: Arc<dyn WorkspaceUser>) -> Module {
         .data(workspace_controller)
         .data(app_controller)
         .event(WorkspaceEvent::CreateWorkspace, create_workspace)
-        .event(WorkspaceEvent::GetWorkspaceUserDetail, workspace_user)
+        .event(WorkspaceEvent::GetWorkspaceDetail, get_workspace_detail)
 }

+ 8 - 8
rust-lib/flowy-workspace/src/protobuf/model/errors.rs

@@ -221,7 +221,7 @@ pub enum WorkspaceErrorCode {
     AppColorStyleInvalid = 3,
     AppIdInvalid = 4,
     DatabaseConnectionFail = 5,
-    DatabaseInternalError = 6,
+    WorkspaceDatabaseError = 6,
     UserInternalError = 10,
     UserNotLoginYet = 11,
 }
@@ -239,7 +239,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
             3 => ::std::option::Option::Some(WorkspaceErrorCode::AppColorStyleInvalid),
             4 => ::std::option::Option::Some(WorkspaceErrorCode::AppIdInvalid),
             5 => ::std::option::Option::Some(WorkspaceErrorCode::DatabaseConnectionFail),
-            6 => ::std::option::Option::Some(WorkspaceErrorCode::DatabaseInternalError),
+            6 => ::std::option::Option::Some(WorkspaceErrorCode::WorkspaceDatabaseError),
             10 => ::std::option::Option::Some(WorkspaceErrorCode::UserInternalError),
             11 => ::std::option::Option::Some(WorkspaceErrorCode::UserNotLoginYet),
             _ => ::std::option::Option::None
@@ -254,7 +254,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
             WorkspaceErrorCode::AppColorStyleInvalid,
             WorkspaceErrorCode::AppIdInvalid,
             WorkspaceErrorCode::DatabaseConnectionFail,
-            WorkspaceErrorCode::DatabaseInternalError,
+            WorkspaceErrorCode::WorkspaceDatabaseError,
             WorkspaceErrorCode::UserInternalError,
             WorkspaceErrorCode::UserNotLoginYet,
         ];
@@ -287,11 +287,11 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"K\n\x0eWorkspaceError\x12'\n\x04code\x18\x01\x20\x01\
     (\x0e2\x13.WorkspaceErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\
-    \tR\x03msg*\xe2\x01\n\x12WorkspaceErrorCode\x12\x0b\n\x07Unknown\x10\0\
+    \tR\x03msg*\xe3\x01\n\x12WorkspaceErrorCode\x12\x0b\n\x07Unknown\x10\0\
     \x12\x18\n\x14WorkspaceNameInvalid\x10\x01\x12\x16\n\x12WorkspaceIdInval\
     id\x10\x02\x12\x18\n\x14AppColorStyleInvalid\x10\x03\x12\x10\n\x0cAppIdI\
-    nvalid\x10\x04\x12\x1a\n\x16DatabaseConnectionFail\x10\x05\x12\x19\n\x15\
-    DatabaseInternalError\x10\x06\x12\x15\n\x11UserInternalError\x10\n\x12\
+    nvalid\x10\x04\x12\x1a\n\x16DatabaseConnectionFail\x10\x05\x12\x1a\n\x16\
+    WorkspaceDatabaseError\x10\x06\x12\x15\n\x11UserInternalError\x10\n\x12\
     \x13\n\x0fUserNotLoginYet\x10\x0bJ\xa1\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\x05\x01\n\n\
     \n\x03\x04\0\x01\x12\x03\x02\x08\x16\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
@@ -313,8 +313,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x03\x0b\x04\x10\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x13\x14\n\x0b\
     \n\x04\x05\0\x02\x05\x12\x03\x0c\x04\x1f\n\x0c\n\x05\x05\0\x02\x05\x01\
     \x12\x03\x0c\x04\x1a\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x1d\x1e\n\
-    \x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x1e\n\x0c\n\x05\x05\0\x02\x06\x01\
-    \x12\x03\r\x04\x19\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x1c\x1d\n\x0b\
+    \x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x1f\n\x0c\n\x05\x05\0\x02\x06\x01\
+    \x12\x03\r\x04\x1a\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x1d\x1e\n\x0b\
     \n\x04\x05\0\x02\x07\x12\x03\x0e\x04\x1b\n\x0c\n\x05\x05\0\x02\x07\x01\
     \x12\x03\x0e\x04\x15\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x18\x1a\n\
     \x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\x04\x19\n\x0c\n\x05\x05\0\x02\x08\

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

@@ -26,7 +26,7 @@
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
 pub enum WorkspaceEvent {
     CreateWorkspace = 0,
-    GetWorkspaceUserDetail = 100,
+    GetWorkspaceDetail = 1,
 }
 
 impl ::protobuf::ProtobufEnum for WorkspaceEvent {
@@ -37,7 +37,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
     fn from_i32(value: i32) -> ::std::option::Option<WorkspaceEvent> {
         match value {
             0 => ::std::option::Option::Some(WorkspaceEvent::CreateWorkspace),
-            100 => ::std::option::Option::Some(WorkspaceEvent::GetWorkspaceUserDetail),
+            1 => ::std::option::Option::Some(WorkspaceEvent::GetWorkspaceDetail),
             _ => ::std::option::Option::None
         }
     }
@@ -45,7 +45,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
     fn values() -> &'static [Self] {
         static values: &'static [WorkspaceEvent] = &[
             WorkspaceEvent::CreateWorkspace,
-            WorkspaceEvent::GetWorkspaceUserDetail,
+            WorkspaceEvent::GetWorkspaceDetail,
         ];
         values
     }
@@ -74,14 +74,14 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*A\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
-    \0\x12\x1a\n\x16GetWorkspaceUserDetail\x10dJ|\n\x06\x12\x04\0\0\x05\x01\
-    \n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x05\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!\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x1a\n\x0c\n\x05\
-    \x05\0\x02\x01\x02\x12\x03\x04\x1d\x20b\x06proto3\
+    \n\x0bevent.proto*=\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorkspace\x10\
+    \0\x12\x16\n\x12GetWorkspaceDetail\x10\x01J|\n\x06\x12\x04\0\0\x05\x01\n\
+    \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x05\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\x1ab\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

@@ -12,6 +12,9 @@ pub use app_create::*;
 mod event; 
 pub use event::*; 
 
+mod workspace_user_detail; 
+pub use workspace_user_detail::*; 
+
 mod workspace_create; 
 pub use workspace_create::*; 
 

+ 476 - 0
rust-lib/flowy-workspace/src/protobuf/model/workspace_user_detail.rs

@@ -0,0 +1,476 @@
+// 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_user_detail.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 UserWorkspace {
+    // message fields
+    pub owner: ::std::string::String,
+    pub workspace_id: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UserWorkspace {
+    fn default() -> &'a UserWorkspace {
+        <UserWorkspace as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl UserWorkspace {
+    pub fn new() -> UserWorkspace {
+        ::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())
+    }
+
+    // string workspace_id = 2;
+
+
+    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())
+    }
+}
+
+impl ::protobuf::Message for UserWorkspace {
+    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.owner)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace_id)?;
+                },
+                _ => {
+                    ::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 !self.workspace_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.workspace_id);
+        }
+        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 !self.workspace_id.is_empty() {
+            os.write_string(2, &self.workspace_id)?;
+        }
+        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() -> UserWorkspace {
+        UserWorkspace::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: &UserWorkspace| { &m.owner },
+                |m: &mut UserWorkspace| { &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 },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UserWorkspace>(
+                "UserWorkspace",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UserWorkspace {
+        static instance: ::protobuf::rt::LazyV2<UserWorkspace> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UserWorkspace::new)
+    }
+}
+
+impl ::protobuf::Clear for UserWorkspace {
+    fn clear(&mut self) {
+        self.owner.clear();
+        self.workspace_id.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for UserWorkspace {
+    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::WorkspaceDetail>,
+    // 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())
+    }
+
+    // .WorkspaceDetail workspace = 2;
+
+
+    pub fn get_workspace(&self) -> &super::workspace_create::WorkspaceDetail {
+        self.workspace.as_ref().unwrap_or_else(|| <super::workspace_create::WorkspaceDetail 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::WorkspaceDetail) {
+        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::WorkspaceDetail {
+        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::WorkspaceDetail {
+        self.workspace.take().unwrap_or_else(|| super::workspace_create::WorkspaceDetail::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::WorkspaceDetail>>(
+                "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 {
+    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\"[\n\x13UserWorkspaceDetai\
+    l\x12\x14\n\x05owner\x18\x01\x20\x01(\tR\x05owner\x12.\n\tworkspace\x18\
+    \x02\x20\x01(\x0b2\x10.WorkspaceDetailR\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\"\n\
+    \x0c\n\x05\x04\x01\x02\x01\x06\x12\x03\t\x04\x13\n\x0c\n\x05\x04\x01\x02\
+    \x01\x01\x12\x03\t\x14\x1d\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\t\x20\
+    !b\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()
+    })
+}

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

@@ -11,7 +11,7 @@ enum WorkspaceErrorCode {
     AppColorStyleInvalid = 3;
     AppIdInvalid = 4;
     DatabaseConnectionFail = 5;
-    DatabaseInternalError = 6;
+    WorkspaceDatabaseError = 6;
     UserInternalError = 10;
     UserNotLoginYet = 11;
 }

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

@@ -2,5 +2,5 @@ syntax = "proto3";
 
 enum WorkspaceEvent {
     CreateWorkspace = 0;
-    GetWorkspaceUserDetail = 100;
+    GetWorkspaceDetail = 1;
 }

+ 11 - 0
rust-lib/flowy-workspace/src/protobuf/proto/workspace_user_detail.proto

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

+ 31 - 1
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -1,6 +1,8 @@
 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 flowy_dispatch::prelude::DispatchFuture;
 use std::sync::Arc;
 
 pub struct WorkspaceController {
@@ -21,11 +23,29 @@ impl WorkspaceController {
             .values(workspace)
             .execute(&*(self.user.db_connection()?))?;
 
-        let _ = self.user.set_workspace(&detail.id).await?;
+        let _ = self.user.set_cur_workspace_id(&detail.id).await?;
 
         Ok(detail)
     }
 
+    pub fn get_workspace(
+        &self,
+        workspace_id: &str,
+    ) -> DispatchFuture<Result<Workspace, WorkspaceError>> {
+        let user = self.user.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::<Workspace>(&*(user.db_connection()?))?;
+
+                // TODO: fetch workspace from remote server
+                Ok(workspace)
+            }),
+        }
+    }
+
     pub fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
         let changeset = WorkspaceChangeset::new(params);
         let conn = self.user.db_connection()?;
@@ -33,4 +53,14 @@ impl WorkspaceController {
 
         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(),
+        })
+    }
 }

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

@@ -1,6 +1,6 @@
 use crate::helper::*;
 use flowy_workspace::{
-    entities::workspace::{CreateWorkspaceRequest, WorkspaceDetail},
+    entities::workspace::{CreateWorkspaceRequest, UserWorkspaceDetail, WorkspaceDetail},
     event::WorkspaceEvent::*,
     prelude::*,
 };
@@ -20,6 +20,27 @@ fn workspace_create_success() {
     dbg!(&response);
 }
 
+#[test]
+fn workspace_get_detail_success() {
+    let request = CreateWorkspaceRequest {
+        name: "Team A".to_owned(),
+        desc: "Team A Description".to_owned(),
+    };
+
+    let workspace = WorkspaceTestBuilder::new()
+        .event(CreateWorkspace)
+        .request(request)
+        .sync_send()
+        .parse::<WorkspaceDetail>();
+
+    let user_workspace = WorkspaceTestBuilder::new()
+        .event(GetWorkspaceDetail)
+        .sync_send()
+        .parse::<UserWorkspaceDetail>();
+
+    assert_eq!(workspace.name, user_workspace.workspace.name);
+}
+
 #[test]
 fn workspace_create_with_invalid_name_test() {
     for name in invalid_workspace_name_test_case() {

+ 2 - 1
scripts/flowy-tool/Cargo.toml

@@ -22,4 +22,5 @@ phf = { version = "0.8.0", features = ["macros"] }
 similar = "1.2.2"
 dialoguer = "0.8.0"
 toml = "0.5.8"
-serde = { version = "1.0", features = ["derive"] }
+serde = { version = "1.0", features = ["derive"] }
+pathdiff = "0.2.0"

+ 1 - 0
scripts/flowy-tool/src/proto/ast.rs

@@ -78,6 +78,7 @@ fn parse_files_protobuf(proto_crate_path: &str, proto_output_dir: &str) -> Vec<P
 
         if !enums.is_empty() || !structs.is_empty() {
             let info = ProtoFile {
+                file_path: path.clone(),
                 file_name: file_name.clone(),
                 structs: structs.iter().map(|s| s.name.clone()).collect(),
                 enums: enums.iter().map(|e| e.name.clone()).collect(),

+ 2 - 0
scripts/flowy-tool/src/proto/proto_info.rs

@@ -88,6 +88,7 @@ impl ProtobufCrate {
 
 #[derive(Debug)]
 pub struct ProtoFile {
+    pub file_path: String,
     pub file_name: String,
     pub structs: Vec<String>,
     pub enums: Vec<String>,
@@ -121,6 +122,7 @@ impl FlutterProtobufInfo {
         model_dir
     }
 
+    #[allow(dead_code)]
     pub fn mod_file_path(&self) -> String {
         let mod_file_path = format!("{}/protobuf.dart", self.package_path);
         mod_file_path

+ 11 - 0
scripts/flowy-tool/src/util/file.rs

@@ -154,3 +154,14 @@ where
         path_and_name(path, name);
     }
 }
+
+#[allow(dead_code)]
+pub fn suffix_relative_to_path(path: &str, base: &str) -> String {
+    let base = Path::new(base);
+    let path = Path::new(path);
+    path.strip_prefix(base)
+        .unwrap()
+        .to_str()
+        .unwrap()
+        .to_owned()
+}