浏览代码

add update user test & add app and workspace test

appflowy 3 年之前
父节点
当前提交
182adafee5
共有 97 个文件被更改,包括 2217 次插入398 次删除
  1. 4 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart
  2. 3 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart
  3. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event.pbenum.dart
  4. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event.pbjson.dart
  5. 1 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/protobuf.dart
  6. 43 15
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_detail.pb.dart
  7. 6 4
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_detail.pbjson.dart
  8. 166 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pb.dart
  9. 7 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pbenum.dart
  10. 30 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pbjson.dart
  11. 9 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pbserver.dart
  12. 89 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pb.dart
  13. 13 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/app_create.pbjson.dart
  14. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart
  15. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart
  16. 2 3
      rust-lib/dart-ffi/src/lib.rs
  17. 2 0
      rust-lib/flowy-database/migrations/2021-07-14-022241_flowy-user/down.sql
  18. 2 0
      rust-lib/flowy-database/migrations/2021-07-14-022241_flowy-user/up.sql
  19. 8 0
      rust-lib/flowy-database/src/lib.rs
  20. 15 0
      rust-lib/flowy-database/src/macros.rs
  21. 1 0
      rust-lib/flowy-database/src/schema.rs
  22. 2 0
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  23. 46 0
      rust-lib/flowy-sdk/src/flowy_server.rs
  24. 34 9
      rust-lib/flowy-sdk/src/lib.rs
  25. 39 5
      rust-lib/flowy-sdk/src/module.rs
  26. 14 10
      rust-lib/flowy-test/src/lib.rs
  27. 6 12
      rust-lib/flowy-user/src/entities/mod.rs
  28. 12 0
      rust-lib/flowy-user/src/entities/parser/mod.rs
  29. 0 0
      rust-lib/flowy-user/src/entities/parser/user_email.rs
  30. 0 0
      rust-lib/flowy-user/src/entities/parser/user_id.rs
  31. 0 0
      rust-lib/flowy-user/src/entities/parser/user_name.rs
  32. 0 0
      rust-lib/flowy-user/src/entities/parser/user_password.rs
  33. 16 0
      rust-lib/flowy-user/src/entities/parser/user_workspace.rs
  34. 1 4
      rust-lib/flowy-user/src/entities/sign_in.rs
  35. 1 1
      rust-lib/flowy-user/src/entities/sign_up.rs
  36. 10 2
      rust-lib/flowy-user/src/entities/user_detail.rs
  37. 106 0
      rust-lib/flowy-user/src/entities/user_update.rs
  38. 4 0
      rust-lib/flowy-user/src/errors.rs
  39. 7 4
      rust-lib/flowy-user/src/event.rs
  40. 2 14
      rust-lib/flowy-user/src/handlers/auth_handler.rs
  41. 3 1
      rust-lib/flowy-user/src/handlers/mod.rs
  42. 24 0
      rust-lib/flowy-user/src/handlers/user_handler.rs
  43. 1 1
      rust-lib/flowy-user/src/lib.rs
  44. 5 4
      rust-lib/flowy-user/src/module.rs
  45. 46 35
      rust-lib/flowy-user/src/protobuf/model/errors.rs
  46. 18 13
      rust-lib/flowy-user/src/protobuf/model/event.rs
  47. 3 0
      rust-lib/flowy-user/src/protobuf/model/mod.rs
  48. 125 34
      rust-lib/flowy-user/src/protobuf/model/user_detail.rs
  49. 549 0
      rust-lib/flowy-user/src/protobuf/model/user_update.rs
  50. 2 0
      rust-lib/flowy-user/src/protobuf/proto/errors.proto
  51. 1 0
      rust-lib/flowy-user/src/protobuf/proto/event.proto
  52. 5 3
      rust-lib/flowy-user/src/protobuf/proto/user_detail.proto
  53. 9 0
      rust-lib/flowy-user/src/protobuf/proto/user_update.proto
  54. 7 4
      rust-lib/flowy-user/src/services/user_session/builder.rs
  55. 1 0
      rust-lib/flowy-user/src/services/user_session/mod.rs
  56. 1 33
      rust-lib/flowy-user/src/services/user_session/user_server.rs
  57. 40 15
      rust-lib/flowy-user/src/services/user_session/user_session.rs
  58. 2 2
      rust-lib/flowy-user/src/sql_tables/mod.rs
  59. 51 0
      rust-lib/flowy-user/src/sql_tables/user.rs
  60. 0 21
      rust-lib/flowy-user/src/sql_tables/user_table.rs
  61. 2 2
      rust-lib/flowy-user/tests/event/helper.rs
  62. 2 6
      rust-lib/flowy-user/tests/event/sign_in_test.rs
  63. 1 1
      rust-lib/flowy-user/tests/event/sign_up_test.rs
  64. 41 2
      rust-lib/flowy-user/tests/event/user_status_test.rs
  65. 1 0
      rust-lib/flowy-workspace/Cargo.toml
  66. 17 2
      rust-lib/flowy-workspace/src/entities/app/app_create.rs
  67. 5 2
      rust-lib/flowy-workspace/src/entities/app/app_update.rs
  68. 4 6
      rust-lib/flowy-workspace/src/entities/app/mod.rs
  69. 0 0
      rust-lib/flowy-workspace/src/entities/app/parser/app_color_style.rs
  70. 0 0
      rust-lib/flowy-workspace/src/entities/app/parser/app_id.rs
  71. 0 0
      rust-lib/flowy-workspace/src/entities/app/parser/app_name.rs
  72. 7 0
      rust-lib/flowy-workspace/src/entities/app/parser/mod.rs
  73. 5 8
      rust-lib/flowy-workspace/src/entities/workspace/mod.rs
  74. 5 0
      rust-lib/flowy-workspace/src/entities/workspace/parser/mod.rs
  75. 0 0
      rust-lib/flowy-workspace/src/entities/workspace/parser/workspace_id.rs
  76. 6 0
      rust-lib/flowy-workspace/src/entities/workspace/parser/workspace_name.rs
  77. 1 1
      rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs
  78. 4 5
      rust-lib/flowy-workspace/src/entities/workspace/workspace_update.rs
  79. 3 0
      rust-lib/flowy-workspace/src/errors.rs
  80. 16 0
      rust-lib/flowy-workspace/src/handlers/app_handler.rs
  81. 2 0
      rust-lib/flowy-workspace/src/handlers/mod.rs
  82. 3 3
      rust-lib/flowy-workspace/src/macros.rs
  83. 19 3
      rust-lib/flowy-workspace/src/module.rs
  84. 311 11
      rust-lib/flowy-workspace/src/protobuf/model/app_create.rs
  85. 30 25
      rust-lib/flowy-workspace/src/protobuf/model/errors.rs
  86. 6 0
      rust-lib/flowy-workspace/src/protobuf/proto/app_create.proto
  87. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/errors.proto
  88. 34 0
      rust-lib/flowy-workspace/src/services/app_controller.rs
  89. 1 0
      rust-lib/flowy-workspace/src/services/helper.rs
  90. 3 0
      rust-lib/flowy-workspace/src/services/mod.rs
  91. 6 58
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  92. 13 2
      rust-lib/flowy-workspace/src/sql_tables/app/app.rs
  93. 2 5
      rust-lib/flowy-workspace/src/sql_tables/workspace/workspace.rs
  94. 15 0
      rust-lib/flowy-workspace/tests/event/app_test.rs
  95. 7 0
      rust-lib/flowy-workspace/tests/event/helper.rs
  96. 1 0
      rust-lib/flowy-workspace/tests/event/main.rs
  97. 41 4
      rust-lib/flowy-workspace/tests/event/workspace_test.rs

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

@@ -22,6 +22,8 @@ class UserErrorCode extends $pb.ProtobufEnum {
   static const UserErrorCode EmailInvalid = UserErrorCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailInvalid');
   static const UserErrorCode PasswordInvalid = UserErrorCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordInvalid');
   static const UserErrorCode UserNameInvalid = UserErrorCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameInvalid');
+  static const UserErrorCode UserWorkspaceInvalid = UserErrorCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserWorkspaceInvalid');
+  static const UserErrorCode UserIdInvalid = UserErrorCode._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
 
   static const $core.List<UserErrorCode> values = <UserErrorCode> [
     Unknown,
@@ -36,6 +38,8 @@ class UserErrorCode extends $pb.ProtobufEnum {
     EmailInvalid,
     PasswordInvalid,
     UserNameInvalid,
+    UserWorkspaceInvalid,
+    UserIdInvalid,
   ];
 
   static final $core.Map<$core.int, UserErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -24,11 +24,13 @@ const UserErrorCode$json = const {
     const {'1': 'EmailInvalid', '2': 20},
     const {'1': 'PasswordInvalid', '2': 21},
     const {'1': 'UserNameInvalid', '2': 22},
+    const {'1': 'UserWorkspaceInvalid', '2': 23},
+    const {'1': 'UserIdInvalid', '2': 24},
   ],
 };
 
 /// Descriptor for `UserErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userErrorCodeDescriptor = $convert.base64Decode('Cg1Vc2VyRXJyb3JDb2RlEgsKB1Vua25vd24QABIWChJEYXRhYmFzZUluaXRGYWlsZWQQARIXChNEYXRhYmFzZVdyaXRlTG9ja2VkEAISFgoSRGF0YWJhc2VSZWFkTG9ja2VkEAMSGwoXRGF0YWJhc2VVc2VyRGlkTm90TWF0Y2gQBBIZChVEYXRhYmFzZUludGVybmFsRXJyb3IQBRITCg9Vc2VyTm90TG9naW5ZZXQQChIXChNSZWFkQ3VycmVudElkRmFpbGVkEAsSGAoUV3JpdGVDdXJyZW50SWRGYWlsZWQQDBIQCgxFbWFpbEludmFsaWQQFBITCg9QYXNzd29yZEludmFsaWQQFRITCg9Vc2VyTmFtZUludmFsaWQQFg==');
+final $typed_data.Uint8List userErrorCodeDescriptor = $convert.base64Decode('Cg1Vc2VyRXJyb3JDb2RlEgsKB1Vua25vd24QABIWChJEYXRhYmFzZUluaXRGYWlsZWQQARIXChNEYXRhYmFzZVdyaXRlTG9ja2VkEAISFgoSRGF0YWJhc2VSZWFkTG9ja2VkEAMSGwoXRGF0YWJhc2VVc2VyRGlkTm90TWF0Y2gQBBIZChVEYXRhYmFzZUludGVybmFsRXJyb3IQBRITCg9Vc2VyTm90TG9naW5ZZXQQChIXChNSZWFkQ3VycmVudElkRmFpbGVkEAsSGAoUV3JpdGVDdXJyZW50SWRGYWlsZWQQDBIQCgxFbWFpbEludmFsaWQQFBITCg9QYXNzd29yZEludmFsaWQQFRITCg9Vc2VyTmFtZUludmFsaWQQFhIYChRVc2VyV29ya3NwYWNlSW52YWxpZBAXEhEKDVVzZXJJZEludmFsaWQQGA==');
 @$core.Deprecated('Use userErrorDescriptor instead')
 const UserError$json = const {
   '1': 'UserError',

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

@@ -14,12 +14,14 @@ class UserEvent extends $pb.ProtobufEnum {
   static const UserEvent SignIn = UserEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SignIn');
   static const UserEvent SignUp = UserEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SignUp');
   static const UserEvent SignOut = UserEvent._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SignOut');
+  static const UserEvent UpdateUser = UserEvent._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateUser');
 
   static const $core.List<UserEvent> values = <UserEvent> [
     GetStatus,
     SignIn,
     SignUp,
     SignOut,
+    UpdateUser,
   ];
 
   static final $core.Map<$core.int, UserEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -16,8 +16,9 @@ const UserEvent$json = const {
     const {'1': 'SignIn', '2': 1},
     const {'1': 'SignUp', '2': 2},
     const {'1': 'SignOut', '2': 3},
+    const {'1': 'UpdateUser', '2': 4},
   ],
 };
 
 /// Descriptor for `UserEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userEventDescriptor = $convert.base64Decode('CglVc2VyRXZlbnQSDQoJR2V0U3RhdHVzEAASCgoGU2lnbkluEAESCgoGU2lnblVwEAISCwoHU2lnbk91dBAD');
+final $typed_data.Uint8List userEventDescriptor = $convert.base64Decode('CglVc2VyRXZlbnQSDQoJR2V0U3RhdHVzEAASCgoGU2lnbkluEAESCgoGU2lnblVwEAISCwoHU2lnbk91dBADEg4KClVwZGF0ZVVzZXIQBA==');

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

@@ -4,4 +4,5 @@ export './sign_in.pb.dart';
 export './user_table.pb.dart';
 export './errors.pb.dart';
 export './user_detail.pb.dart';
+export './user_update.pb.dart';
 export './event.pb.dart';

+ 43 - 15
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_detail.pb.dart

@@ -15,19 +15,26 @@ export 'user_detail.pbenum.dart';
 
 class UserDetail extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserDetail', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'email')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
-    ..e<UserStatus>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'status', $pb.PbFieldType.OE, defaultOrMaker: UserStatus.Unknown, valueOf: UserStatus.valueOf, enumValues: UserStatus.values)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'email')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..e<UserStatus>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'status', $pb.PbFieldType.OE, defaultOrMaker: UserStatus.Unknown, valueOf: UserStatus.valueOf, enumValues: UserStatus.values)
+    ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspace')
     ..hasRequiredFields = false
   ;
 
   UserDetail._() : super();
   factory UserDetail({
+    $core.String? id,
     $core.String? email,
     $core.String? name,
     UserStatus? status,
+    $core.String? workspace,
   }) {
     final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
     if (email != null) {
       _result.email = email;
     }
@@ -37,6 +44,9 @@ class UserDetail extends $pb.GeneratedMessage {
     if (status != null) {
       _result.status = status;
     }
+    if (workspace != null) {
+      _result.workspace = workspace;
+    }
     return _result;
   }
   factory UserDetail.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -61,30 +71,48 @@ class UserDetail extends $pb.GeneratedMessage {
   static UserDetail? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get email => $_getSZ(0);
+  $core.String get id => $_getSZ(0);
   @$pb.TagNumber(1)
-  set email($core.String v) { $_setString(0, v); }
+  set id($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasEmail() => $_has(0);
+  $core.bool hasId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearEmail() => clearField(1);
+  void clearId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.String get name => $_getSZ(1);
+  $core.String get email => $_getSZ(1);
   @$pb.TagNumber(2)
-  set name($core.String v) { $_setString(1, v); }
+  set email($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasName() => $_has(1);
+  $core.bool hasEmail() => $_has(1);
   @$pb.TagNumber(2)
-  void clearName() => clearField(2);
+  void clearEmail() => clearField(2);
 
   @$pb.TagNumber(3)
-  UserStatus get status => $_getN(2);
+  $core.String get name => $_getSZ(2);
   @$pb.TagNumber(3)
-  set status(UserStatus v) { setField(3, v); }
+  set name($core.String v) { $_setString(2, v); }
   @$pb.TagNumber(3)
-  $core.bool hasStatus() => $_has(2);
+  $core.bool hasName() => $_has(2);
   @$pb.TagNumber(3)
-  void clearStatus() => clearField(3);
+  void clearName() => clearField(3);
+
+  @$pb.TagNumber(4)
+  UserStatus get status => $_getN(3);
+  @$pb.TagNumber(4)
+  set status(UserStatus v) { setField(4, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasStatus() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearStatus() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.String get workspace => $_getSZ(4);
+  @$pb.TagNumber(5)
+  set workspace($core.String v) { $_setString(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasWorkspace() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearWorkspace() => clearField(5);
 }
 

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

@@ -24,11 +24,13 @@ final $typed_data.Uint8List userStatusDescriptor = $convert.base64Decode('CgpVc2
 const UserDetail$json = const {
   '1': 'UserDetail',
   '2': const [
-    const {'1': 'email', '3': 1, '4': 1, '5': 9, '10': 'email'},
-    const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
-    const {'1': 'status', '3': 3, '4': 1, '5': 14, '6': '.UserStatus', '10': 'status'},
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'email', '3': 2, '4': 1, '5': 9, '10': 'email'},
+    const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
+    const {'1': 'status', '3': 4, '4': 1, '5': 14, '6': '.UserStatus', '10': 'status'},
+    const {'1': 'workspace', '3': 5, '4': 1, '5': 9, '10': 'workspace'},
   ],
 };
 
 /// Descriptor for `UserDetail`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List userDetailDescriptor = $convert.base64Decode('CgpVc2VyRGV0YWlsEhQKBWVtYWlsGAEgASgJUgVlbWFpbBISCgRuYW1lGAIgASgJUgRuYW1lEiMKBnN0YXR1cxgDIAEoDjILLlVzZXJTdGF0dXNSBnN0YXR1cw==');
+final $typed_data.Uint8List userDetailDescriptor = $convert.base64Decode('CgpVc2VyRGV0YWlsEg4KAmlkGAEgASgJUgJpZBIUCgVlbWFpbBgCIAEoCVIFZW1haWwSEgoEbmFtZRgDIAEoCVIEbmFtZRIjCgZzdGF0dXMYBCABKA4yCy5Vc2VyU3RhdHVzUgZzdGF0dXMSHAoJd29ya3NwYWNlGAUgASgJUgl3b3Jrc3BhY2U=');

+ 166 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pb.dart

@@ -0,0 +1,166 @@
+///
+//  Generated code. Do not modify.
+//  source: user_update.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;
+
+enum UpdateUserRequest_OneOfName {
+  name, 
+  notSet
+}
+
+enum UpdateUserRequest_OneOfEmail {
+  email, 
+  notSet
+}
+
+enum UpdateUserRequest_OneOfWorkspace {
+  workspace, 
+  notSet
+}
+
+enum UpdateUserRequest_OneOfPassword {
+  password, 
+  notSet
+}
+
+class UpdateUserRequest extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, UpdateUserRequest_OneOfName> _UpdateUserRequest_OneOfNameByTag = {
+    2 : UpdateUserRequest_OneOfName.name,
+    0 : UpdateUserRequest_OneOfName.notSet
+  };
+  static const $core.Map<$core.int, UpdateUserRequest_OneOfEmail> _UpdateUserRequest_OneOfEmailByTag = {
+    3 : UpdateUserRequest_OneOfEmail.email,
+    0 : UpdateUserRequest_OneOfEmail.notSet
+  };
+  static const $core.Map<$core.int, UpdateUserRequest_OneOfWorkspace> _UpdateUserRequest_OneOfWorkspaceByTag = {
+    4 : UpdateUserRequest_OneOfWorkspace.workspace,
+    0 : UpdateUserRequest_OneOfWorkspace.notSet
+  };
+  static const $core.Map<$core.int, UpdateUserRequest_OneOfPassword> _UpdateUserRequest_OneOfPasswordByTag = {
+    5 : UpdateUserRequest_OneOfPassword.password,
+    0 : UpdateUserRequest_OneOfPassword.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateUserRequest', createEmptyInstance: create)
+    ..oo(0, [2])
+    ..oo(1, [3])
+    ..oo(2, [4])
+    ..oo(3, [5])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'email')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspace')
+    ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'password')
+    ..hasRequiredFields = false
+  ;
+
+  UpdateUserRequest._() : super();
+  factory UpdateUserRequest({
+    $core.String? id,
+    $core.String? name,
+    $core.String? email,
+    $core.String? workspace,
+    $core.String? password,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (email != null) {
+      _result.email = email;
+    }
+    if (workspace != null) {
+      _result.workspace = workspace;
+    }
+    if (password != null) {
+      _result.password = password;
+    }
+    return _result;
+  }
+  factory UpdateUserRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory UpdateUserRequest.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')
+  UpdateUserRequest clone() => UpdateUserRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  UpdateUserRequest copyWith(void Function(UpdateUserRequest) updates) => super.copyWith((message) => updates(message as UpdateUserRequest)) as UpdateUserRequest; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static UpdateUserRequest create() => UpdateUserRequest._();
+  UpdateUserRequest createEmptyInstance() => create();
+  static $pb.PbList<UpdateUserRequest> createRepeated() => $pb.PbList<UpdateUserRequest>();
+  @$core.pragma('dart2js:noInline')
+  static UpdateUserRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateUserRequest>(create);
+  static UpdateUserRequest? _defaultInstance;
+
+  UpdateUserRequest_OneOfName whichOneOfName() => _UpdateUserRequest_OneOfNameByTag[$_whichOneof(0)]!;
+  void clearOneOfName() => clearField($_whichOneof(0));
+
+  UpdateUserRequest_OneOfEmail whichOneOfEmail() => _UpdateUserRequest_OneOfEmailByTag[$_whichOneof(1)]!;
+  void clearOneOfEmail() => clearField($_whichOneof(1));
+
+  UpdateUserRequest_OneOfWorkspace whichOneOfWorkspace() => _UpdateUserRequest_OneOfWorkspaceByTag[$_whichOneof(2)]!;
+  void clearOneOfWorkspace() => clearField($_whichOneof(2));
+
+  UpdateUserRequest_OneOfPassword whichOneOfPassword() => _UpdateUserRequest_OneOfPasswordByTag[$_whichOneof(3)]!;
+  void clearOneOfPassword() => clearField($_whichOneof(3));
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get name => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set name($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasName() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearName() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get email => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set email($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasEmail() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearEmail() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.String get workspace => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set workspace($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasWorkspace() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearWorkspace() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.String get password => $_getSZ(4);
+  @$pb.TagNumber(5)
+  set password($core.String v) { $_setString(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasPassword() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearPassword() => clearField(5);
+}
+

+ 7 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pbenum.dart

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

+ 30 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pbjson.dart

@@ -0,0 +1,30 @@
+///
+//  Generated code. Do not modify.
+//  source: user_update.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 updateUserRequestDescriptor instead')
+const UpdateUserRequest$json = const {
+  '1': 'UpdateUserRequest',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'name', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'name'},
+    const {'1': 'email', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'email'},
+    const {'1': 'workspace', '3': 4, '4': 1, '5': 9, '9': 2, '10': 'workspace'},
+    const {'1': 'password', '3': 5, '4': 1, '5': 9, '9': 3, '10': 'password'},
+  ],
+  '8': const [
+    const {'1': 'one_of_name'},
+    const {'1': 'one_of_email'},
+    const {'1': 'one_of_workspace'},
+    const {'1': 'one_of_password'},
+  ],
+};
+
+/// Descriptor for `UpdateUserRequest`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List updateUserRequestDescriptor = $convert.base64Decode('ChFVcGRhdGVVc2VyUmVxdWVzdBIOCgJpZBgBIAEoCVICaWQSFAoEbmFtZRgCIAEoCUgAUgRuYW1lEhYKBWVtYWlsGAMgASgJSAFSBWVtYWlsEh4KCXdvcmtzcGFjZRgEIAEoCUgCUgl3b3Jrc3BhY2USHAoIcGFzc3dvcmQYBSABKAlIA1IIcGFzc3dvcmRCDQoLb25lX29mX25hbWVCDgoMb25lX29mX2VtYWlsQhIKEG9uZV9vZl93b3Jrc3BhY2VCEQoPb25lX29mX3Bhc3N3b3Jk');

+ 9 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pbserver.dart

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

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

@@ -147,3 +147,92 @@ class ColorStyle extends $pb.GeneratedMessage {
   void clearThemeColor() => clearField(1);
 }
 
+class AppDetail extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'AppDetail', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspaceId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
+    ..hasRequiredFields = false
+  ;
+
+  AppDetail._() : super();
+  factory AppDetail({
+    $core.String? id,
+    $core.String? workspaceId,
+    $core.String? name,
+    $core.String? desc,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (workspaceId != null) {
+      _result.workspaceId = workspaceId;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (desc != null) {
+      _result.desc = desc;
+    }
+    return _result;
+  }
+  factory AppDetail.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory AppDetail.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')
+  AppDetail clone() => AppDetail()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  AppDetail copyWith(void Function(AppDetail) updates) => super.copyWith((message) => updates(message as AppDetail)) as AppDetail; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static AppDetail create() => AppDetail._();
+  AppDetail createEmptyInstance() => create();
+  static $pb.PbList<AppDetail> createRepeated() => $pb.PbList<AppDetail>();
+  @$core.pragma('dart2js:noInline')
+  static AppDetail getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AppDetail>(create);
+  static AppDetail? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => 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);
+
+  @$pb.TagNumber(3)
+  $core.String get name => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set name($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasName() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearName() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.String get desc => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set desc($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasDesc() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearDesc() => clearField(4);
+}
+

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

@@ -31,3 +31,16 @@ const ColorStyle$json = const {
 
 /// Descriptor for `ColorStyle`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List colorStyleDescriptor = $convert.base64Decode('CgpDb2xvclN0eWxlEh8KC3RoZW1lX2NvbG9yGAEgASgJUgp0aGVtZUNvbG9y');
+@$core.Deprecated('Use appDetailDescriptor instead')
+const AppDetail$json = const {
+  '1': 'AppDetail',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'workspace_id', '3': 2, '4': 1, '5': 9, '10': 'workspaceId'},
+    const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
+    const {'1': 'desc', '3': 4, '4': 1, '5': 9, '10': 'desc'},
+  ],
+};
+
+/// Descriptor for `AppDetail`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List appDetailDescriptor = $convert.base64Decode('CglBcHBEZXRhaWwSDgoCaWQYASABKAlSAmlkEiEKDHdvcmtzcGFjZV9pZBgCIAEoCVILd29ya3NwYWNlSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNj');

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

@@ -17,6 +17,7 @@ class WorkspaceErrorCode extends $pb.ProtobufEnum {
   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 UserInternalError = WorkspaceErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserInternalError');
 
   static const $core.List<WorkspaceErrorCode> values = <WorkspaceErrorCode> [
     Unknown,
@@ -26,6 +27,7 @@ class WorkspaceErrorCode extends $pb.ProtobufEnum {
     AppIdInvalid,
     DatabaseConnectionFail,
     DatabaseInternalError,
+    UserInternalError,
   ];
 
   static final $core.Map<$core.int, WorkspaceErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

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

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

+ 2 - 3
rust-lib/dart-ffi/src/lib.rs

@@ -24,10 +24,9 @@ lazy_static! {
 pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
     let c_str: &CStr = unsafe { CStr::from_ptr(path) };
     let path: &str = c_str.to_str().unwrap();
-    FlowySDK::init_log(path);
-
     log::info!("🔥 FlowySDK start running");
-    FlowySDK::init(path);
+    FlowySDK::new(path).construct();
+
     return 1;
 }
 

+ 2 - 0
rust-lib/flowy-database/migrations/2021-07-14-022241_flowy-user/down.sql

@@ -0,0 +1,2 @@
+-- This file should undo anything in `up.sql`
+DROP TABLE user_table;

+ 2 - 0
rust-lib/flowy-database/migrations/2021-07-14-022241_flowy-user/up.sql

@@ -0,0 +1,2 @@
+-- Your SQL goes here
+ALTER TABLE user_table ADD COLUMN workspace TEXT NOT NULL DEFAULT '';

+ 8 - 0
rust-lib/flowy-database/src/lib.rs

@@ -1,5 +1,8 @@
 pub mod schema;
 
+#[macro_use]
+pub mod macros;
+
 #[macro_use]
 extern crate diesel;
 pub use diesel::*;
@@ -17,6 +20,11 @@ use diesel_migrations::*;
 use flowy_sqlite::{Error, PoolConfig};
 use std::{fmt::Debug, io, path::Path};
 
+pub mod prelude {
+    pub use super::UserDatabaseConnection;
+    pub use diesel::{query_dsl::*, ExpressionMethods, RunQueryDsl};
+}
+
 embed_migrations!("../flowy-database/migrations/");
 pub const DB_NAME: &str = "flowy-database.db";
 

+ 15 - 0
rust-lib/flowy-database/src/macros.rs

@@ -0,0 +1,15 @@
+#[macro_export]
+macro_rules! diesel_update_table {
+    (
+        $table_name:ident,
+        $changeset:ident,
+        $connection:ident
+    ) => {
+        let filter =
+            $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($changeset.id.clone()));
+        let affected_row = diesel::update(filter)
+            .set($changeset)
+            .execute(&*$connection)?;
+        debug_assert_eq!(affected_row, 1);
+    };
+}

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

@@ -18,6 +18,7 @@ table! {
         name -> Text,
         password -> Text,
         email -> Text,
+        workspace -> Text,
     }
 }
 

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

@@ -18,6 +18,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         "KeyValue"
         | "CreateAppRequest"
         | "ColorStyle"
+        | "AppDetail"
         | "UpdateAppRequest"
         | "UpdateWorkspaceRequest"
         | "CreateWorkspaceRequest"
@@ -26,6 +27,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "FFIRequest"
         | "FFIResponse"
         | "UserDetail"
+        | "UpdateUserRequest"
         | "SignUpRequest"
         | "SignUpParams"
         | "SignUpResponse"

+ 46 - 0
rust-lib/flowy-sdk/src/flowy_server.rs

@@ -0,0 +1,46 @@
+use flowy_infra::uuid;
+use flowy_user::{
+    entities::{SignInParams, SignUpParams, UserDetail},
+    errors::{ErrorBuilder, UserError, UserErrorCode},
+    prelude::UserServer,
+    sql_tables::User,
+};
+
+pub type ArcFlowyServer = std::sync::Arc<dyn FlowyServer>;
+
+pub trait FlowyServer: UserServer {}
+
+pub struct MockFlowyServer {}
+
+impl FlowyServer for MockFlowyServer {}
+
+impl UserServer for MockFlowyServer {
+    fn sign_up(&self, params: SignUpParams) -> Result<User, UserError> {
+        // let user_id = "9527".to_owned();
+        let user_id = uuid();
+        Ok(User::new(
+            user_id,
+            params.name,
+            params.email,
+            params.password,
+        ))
+    }
+
+    fn sign_in(&self, params: SignInParams) -> Result<User, UserError> {
+        let user_id = uuid();
+        Ok(User::new(
+            user_id,
+            "".to_owned(),
+            params.email,
+            params.password,
+        ))
+    }
+
+    fn get_user_info(&self, _user_id: &str) -> Result<UserDetail, UserError> {
+        Err(ErrorBuilder::new(UserErrorCode::Unknown).build())
+    }
+
+    fn sign_out(&self, _user_id: &str) -> Result<(), UserError> {
+        Err(ErrorBuilder::new(UserErrorCode::Unknown).build())
+    }
+}

+ 34 - 9
rust-lib/flowy-sdk/src/lib.rs

@@ -1,27 +1,52 @@
+mod flowy_server;
 pub mod module;
-pub use module::*;
 
+use crate::{
+    flowy_server::{ArcFlowyServer, MockFlowyServer},
+    user_server::MockUserServer,
+};
 use flowy_dispatch::prelude::*;
 use module::build_modules;
-use std::sync::atomic::{AtomicBool, Ordering};
+pub use module::*;
+use std::sync::{
+    atomic::{AtomicBool, Ordering},
+    Arc,
+};
 
 static INIT_LOG: AtomicBool = AtomicBool::new(false);
-pub struct FlowySDK {}
+pub struct FlowySDK {
+    root: String,
+    server: ArcFlowyServer,
+}
 
 impl FlowySDK {
-    pub fn init_log(directory: &str) {
+    pub fn new(root: &str) -> Self {
+        let server = Arc::new(MockFlowyServer {});
+        Self {
+            root: root.to_owned(),
+            server,
+        }
+    }
+
+    pub fn construct(self) {
+        FlowySDK::init_log(&self.root);
+
+        tracing::info!("🔥 Root path: {}", self.root);
+        flowy_infra::kv::KVStore::init(path);
+        FlowySDK::init_modules(&self.root, self.server);
+    }
+
+    fn init_log(directory: &str) {
         if !INIT_LOG.load(Ordering::SeqCst) {
             INIT_LOG.store(true, Ordering::SeqCst);
             flowy_log::init_log("flowy", directory, "Debug").unwrap();
         }
     }
 
-    pub fn init(path: &str) {
-        tracing::info!("🔥 Root path: {}", path);
-        flowy_infra::kv::KVStore::init(path);
+    fn init_modules(root: &str, server: ArcFlowyServer) {
         let config = ModuleConfig {
-            root: path.to_string(),
+            root: root.to_owned(),
         };
-        EventDispatch::construct(|| build_modules(config));
+        EventDispatch::construct(|| build_modules(config, server));
     }
 }

+ 39 - 5
rust-lib/flowy-sdk/src/module.rs

@@ -1,6 +1,9 @@
-use flowy_database::{DBConnection, UserDatabaseConnection};
 use flowy_dispatch::prelude::Module;
 use flowy_user::prelude::*;
+
+use crate::{flowy_server::ArcFlowyServer, user_server::MockUserServer};
+use flowy_database::DBConnection;
+use flowy_user::errors::UserError;
 use flowy_workspace::prelude::*;
 use std::sync::Arc;
 
@@ -8,12 +11,43 @@ pub struct ModuleConfig {
     pub root: String,
 }
 
-pub fn build_modules(config: ModuleConfig) -> Vec<Module> {
-    let user_session = Arc::new(UserSessionBuilder::new().root_dir(&config.root).build());
-    let controller = Arc::new(WorkspaceController::new(user_session.clone()));
+pub fn build_modules(config: ModuleConfig, server: ArcFlowyServer) -> Vec<Module> {
+    let user_session = Arc::new(
+        UserSessionBuilder::new()
+            .root_dir(&config.root)
+            .build(config.server),
+    );
+
+    let workspace_user_impl = Arc::new(WorkspaceUserImpl {
+        user_session: user_session.clone(),
+    });
 
     vec![
         flowy_user::module::create(user_session),
-        flowy_workspace::module::create(controller),
+        flowy_workspace::module::create(workspace_user_impl),
     ]
 }
+
+pub struct WorkspaceUserImpl {
+    user_session: Arc<UserSession>,
+}
+
+impl WorkspaceUser for WorkspaceUserImpl {
+    fn set_current_workspace(&self, id: &str) { unimplemented!() }
+
+    fn get_current_workspace(&self) -> Result<String, WorkspaceError> {
+        self.user_session.get_current_workspace().map_err(|e| {
+            ErrorBuilder::new(WorkspaceErrorCode::UserInternalError)
+                .error(e)
+                .build()
+        })
+    }
+
+    fn db_connection(&self) -> Result<DBConnection, WorkspaceError> {
+        self.user_session.get_db_connection().map_err(|e| {
+            ErrorBuilder::new(WorkspaceErrorCode::DatabaseConnectionFail)
+                .error(e)
+                .build()
+        })
+    }
+}

+ 14 - 10
rust-lib/flowy-test/src/lib.rs

@@ -22,8 +22,7 @@ pub fn init_sdk() {
     let root_dir = root_dir();
 
     INIT.call_once(|| {
-        FlowySDK::init_log(&root_dir);
-        FlowySDK::init(&root_dir);
+        FlowySDK::new(&root_dir).construct();
     });
 }
 
@@ -43,16 +42,20 @@ fn root_dir() -> String {
     root_dir
 }
 
-pub struct EventTester<ErrType> {
+pub trait TesterConfig {
+    fn auto_sign_in() -> bool { false }
+}
+
+pub struct EventTester<Error> {
     inner_request: Option<ModuleRequest>,
     assert_status_code: Option<StatusCode>,
     response: Option<EventResponse>,
-    phantom: PhantomData<ErrType>,
+    err_phantom: PhantomData<Error>,
 }
 
-impl<ErrType> EventTester<ErrType>
+impl<Error> EventTester<Error>
 where
-    ErrType: FromBytes + Debug,
+    Error: FromBytes + Debug,
 {
     pub fn new<E>(event: E) -> Self
     where
@@ -64,11 +67,12 @@ where
             thread::current(),
             thread_id::get()
         );
+
         Self {
             inner_request: Some(ModuleRequest::new(event)),
             assert_status_code: None,
             response: None,
-            phantom: PhantomData,
+            err_phantom: PhantomData,
         }
     }
 
@@ -117,7 +121,7 @@ where
     {
         let response = self.response.unwrap();
         if response.status_code == StatusCode::Err {
-            let error = <Data<ErrType>>::try_from(response.payload)
+            let error = <Data<Error>>::try_from(response.payload)
                 .unwrap()
                 .into_inner();
             dbg!(&error);
@@ -127,10 +131,10 @@ where
         }
     }
 
-    pub fn error(self) -> ErrType {
+    pub fn error(self) -> Error {
         let response = self.response.unwrap();
         assert_eq!(response.status_code, StatusCode::Err);
-        <Data<ErrType>>::try_from(response.payload)
+        <Data<Error>>::try_from(response.payload)
             .unwrap()
             .into_inner()
     }

+ 6 - 12
rust-lib/flowy-user/src/entities/mod.rs

@@ -1,15 +1,9 @@
-mod sign_in;
-mod sign_up;
-mod user_detail;
-mod user_email;
-mod user_id;
-mod user_name;
-mod user_password;
-
 pub use sign_in::*;
 pub use sign_up::*;
 pub use user_detail::*;
-pub use user_email::*;
-pub use user_id::*;
-pub use user_name::*;
-pub use user_password::*;
+pub use user_update::*;
+mod parser;
+mod sign_in;
+mod sign_up;
+mod user_detail;
+mod user_update;

+ 12 - 0
rust-lib/flowy-user/src/entities/parser/mod.rs

@@ -0,0 +1,12 @@
+// https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
+mod user_email;
+mod user_id;
+mod user_name;
+mod user_password;
+mod user_workspace;
+
+pub use user_email::*;
+pub use user_id::*;
+pub use user_name::*;
+pub use user_password::*;
+pub use user_workspace::*;

+ 0 - 0
rust-lib/flowy-user/src/entities/user_email.rs → rust-lib/flowy-user/src/entities/parser/user_email.rs


+ 0 - 0
rust-lib/flowy-user/src/entities/user_id.rs → rust-lib/flowy-user/src/entities/parser/user_id.rs


+ 0 - 0
rust-lib/flowy-user/src/entities/user_name.rs → rust-lib/flowy-user/src/entities/parser/user_name.rs


+ 0 - 0
rust-lib/flowy-user/src/entities/user_password.rs → rust-lib/flowy-user/src/entities/parser/user_password.rs


+ 16 - 0
rust-lib/flowy-user/src/entities/parser/user_workspace.rs

@@ -0,0 +1,16 @@
+#[derive(Debug)]
+pub struct UserWorkspace(pub String);
+
+impl UserWorkspace {
+    pub fn parse(s: String) -> Result<UserWorkspace, String> {
+        let is_empty_or_whitespace = s.trim().is_empty();
+        if is_empty_or_whitespace {
+            return Err(format!("workspace id is empty or whitespace"));
+        }
+        Ok(Self(s))
+    }
+}
+
+impl AsRef<str> for UserWorkspace {
+    fn as_ref(&self) -> &str { &self.0 }
+}

+ 1 - 4
rust-lib/flowy-user/src/entities/sign_in.rs

@@ -1,7 +1,4 @@
-use crate::{
-    entities::{UserEmail, UserPassword},
-    errors::*,
-};
+use crate::{entities::parser::*, errors::*};
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
 

+ 1 - 1
rust-lib/flowy-user/src/entities/sign_up.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::{UserEmail, UserName, UserPassword},
+    entities::parser::*,
     errors::{ErrorBuilder, UserError, UserErrorCode},
 };
 use flowy_derive::ProtoBuf;

+ 10 - 2
rust-lib/flowy-user/src/entities/user_detail.rs

@@ -14,22 +14,30 @@ impl std::default::Default for UserStatus {
 #[derive(ProtoBuf, Default, Debug)]
 pub struct UserDetail {
     #[pb(index = 1)]
-    pub email: String,
+    pub id: String,
 
     #[pb(index = 2)]
-    pub name: String,
+    pub email: String,
 
     #[pb(index = 3)]
+    pub name: String,
+
+    #[pb(index = 4)]
     pub status: UserStatus,
+
+    #[pb(index = 5)]
+    pub workspace: String,
 }
 
 use crate::sql_tables::User;
 impl std::convert::From<User> for UserDetail {
     fn from(user: User) -> Self {
         UserDetail {
+            id: user.id,
             email: user.email,
             name: user.name,
             status: UserStatus::Login,
+            workspace: user.workspace,
         }
     }
 }

+ 106 - 0
rust-lib/flowy-user/src/entities/user_update.rs

@@ -0,0 +1,106 @@
+use crate::{
+    entities::parser::*,
+    errors::{ErrorBuilder, UserError, UserErrorCode},
+};
+use flowy_derive::ProtoBuf;
+use std::convert::TryInto;
+
+#[derive(ProtoBuf, Default)]
+pub struct UpdateUserRequest {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2, one_of)]
+    pub name: Option<String>,
+
+    #[pb(index = 3, one_of)]
+    pub email: Option<String>,
+
+    #[pb(index = 4, one_of)]
+    pub workspace: Option<String>,
+
+    #[pb(index = 5, one_of)]
+    pub password: Option<String>,
+}
+
+pub struct UpdateUserParams {
+    pub id: String,
+    pub name: Option<String>,
+    pub email: Option<String>,
+    pub workspace: Option<String>,
+    pub password: Option<String>,
+}
+
+impl TryInto<UpdateUserParams> for UpdateUserRequest {
+    type Error = UserError;
+
+    fn try_into(self) -> Result<UpdateUserParams, Self::Error> {
+        let id = UserId::parse(self.id)
+            .map_err(|e| {
+                ErrorBuilder::new(UserErrorCode::UserIdInvalid)
+                    .msg(e)
+                    .build()
+            })?
+            .0;
+
+        let name = match self.name {
+            None => None,
+            Some(name) => Some(
+                UserName::parse(name)
+                    .map_err(|e| {
+                        ErrorBuilder::new(UserErrorCode::UserNameInvalid)
+                            .msg(e)
+                            .build()
+                    })?
+                    .0,
+            ),
+        };
+
+        let email = match self.email {
+            None => None,
+            Some(email) => Some(
+                UserEmail::parse(email)
+                    .map_err(|e| {
+                        ErrorBuilder::new(UserErrorCode::EmailInvalid)
+                            .msg(e)
+                            .build()
+                    })?
+                    .0,
+            ),
+        };
+
+        let workspace = match self.workspace {
+            None => None,
+            Some(workspace) => Some(
+                UserWorkspace::parse(workspace)
+                    .map_err(|e| {
+                        ErrorBuilder::new(UserErrorCode::UserWorkspaceInvalid)
+                            .msg(e)
+                            .build()
+                    })?
+                    .0,
+            ),
+        };
+
+        let password = match self.password {
+            None => None,
+            Some(password) => Some(
+                UserPassword::parse(password)
+                    .map_err(|e| {
+                        ErrorBuilder::new(UserErrorCode::PasswordInvalid)
+                            .msg(e)
+                            .build()
+                    })?
+                    .0,
+            ),
+        };
+
+        Ok(UpdateUserParams {
+            id,
+            name,
+            email,
+            workspace,
+            password,
+        })
+    }
+}

+ 4 - 0
rust-lib/flowy-user/src/errors.rs

@@ -49,6 +49,10 @@ pub enum UserErrorCode {
     PasswordInvalid      = 21,
     #[display(fmt = "User is invalid")]
     UserNameInvalid      = 22,
+    #[display(fmt = "User workspace is invalid")]
+    UserWorkspaceInvalid = 23,
+    #[display(fmt = "User id is invalid")]
+    UserIdInvalid        = 24,
 }
 
 impl std::default::Default for UserErrorCode {

+ 7 - 4
rust-lib/flowy-user/src/event.rs

@@ -6,14 +6,17 @@ use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
 pub enum UserEvent {
     #[display(fmt = "GetStatus")]
     #[event(output = "UserDetail")]
-    GetStatus = 0,
+    GetStatus  = 0,
     #[display(fmt = "SignIn")]
     #[event(input = "SignInRequest", output = "UserDetail")]
-    SignIn    = 1,
+    SignIn     = 1,
     #[display(fmt = "SignUp")]
     #[event(input = "SignUpRequest", output = "UserDetail")]
-    SignUp    = 2,
+    SignUp     = 2,
     #[display(fmt = "SignOut")]
     #[event(passthrough)]
-    SignOut   = 3,
+    SignOut    = 3,
+    #[display(fmt = "UpdateUser")]
+    #[event(input = "UpdateUserRequest", output = "UserDetail")]
+    UpdateUser = 4,
 }

+ 2 - 14
rust-lib/flowy-user/src/handlers/auth_handler.rs

@@ -10,7 +10,7 @@ use std::{convert::TryInto, sync::Arc};
         email = %data.email,
     )
 )]
-pub async fn user_sign_in(
+pub async fn user_sign_in_handler(
     data: Data<SignInRequest>,
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
@@ -28,7 +28,7 @@ pub async fn user_sign_in(
         name = %data.name,
     )
 )]
-pub async fn user_sign_up(
+pub async fn user_sign_up_handler(
     data: Data<SignUpRequest>,
     session: ModuleData<Arc<UserSession>>,
 ) -> ResponseResult<UserDetail, UserError> {
@@ -37,15 +37,3 @@ pub async fn user_sign_up(
     let user_detail = UserDetail::from(user);
     response_ok(user_detail)
 }
-
-pub async fn user_get_status(
-    session: ModuleData<Arc<UserSession>>,
-) -> ResponseResult<UserDetail, UserError> {
-    let user_detail = session.current_user_detail().await?;
-    response_ok(user_detail)
-}
-
-pub async fn user_sign_out(session: ModuleData<Arc<UserSession>>) -> Result<(), UserError> {
-    let _ = session.sign_out().await?;
-    Ok(())
-}

+ 3 - 1
rust-lib/flowy-user/src/handlers/mod.rs

@@ -1,3 +1,5 @@
-pub mod auth_handler;
+mod auth_handler;
+mod user_handler;
 
 pub use auth_handler::*;
+pub use user_handler::*;

+ 24 - 0
rust-lib/flowy-user/src/handlers/user_handler.rs

@@ -0,0 +1,24 @@
+use crate::{entities::*, errors::UserError, services::user_session::UserSession};
+use flowy_dispatch::prelude::*;
+use std::{convert::TryInto, sync::Arc};
+
+pub async fn user_get_status_handler(
+    session: ModuleData<Arc<UserSession>>,
+) -> ResponseResult<UserDetail, UserError> {
+    let user_detail = session.current_user_detail()?;
+    response_ok(user_detail)
+}
+
+pub async fn sign_out_handler(session: ModuleData<Arc<UserSession>>) -> Result<(), UserError> {
+    let _ = session.sign_out().await?;
+    Ok(())
+}
+
+pub async fn update_user_handler(
+    data: Data<UpdateUserRequest>,
+    session: ModuleData<Arc<UserSession>>,
+) -> ResponseResult<UserDetail, UserError> {
+    let params: UpdateUserParams = data.into_inner().try_into()?;
+    let user_detail = session.update_user(params).await?;
+    response_ok(user_detail)
+}

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

@@ -11,5 +11,5 @@ pub mod sql_tables;
 extern crate flowy_database;
 
 pub mod prelude {
-    pub use crate::{entities::*, handlers::auth_handler::*, services::user_session::*};
+    pub use crate::{entities::*, handlers::*, services::user_session::*};
 }

+ 5 - 4
rust-lib/flowy-user/src/module.rs

@@ -7,8 +7,9 @@ pub fn create(user_session: Arc<UserSession>) -> Module {
     Module::new()
         .name("Flowy-User")
         .data(user_session)
-        .event(UserEvent::SignIn, user_sign_in)
-        .event(UserEvent::SignUp, user_sign_up)
-        .event(UserEvent::GetStatus, user_get_status)
-        .event(UserEvent::SignOut, user_sign_out)
+        .event(UserEvent::SignIn, user_sign_in_handler)
+        .event(UserEvent::SignUp, user_sign_up_handler)
+        .event(UserEvent::GetStatus, user_get_status_handler)
+        .event(UserEvent::SignOut, sign_out_handler)
+        .event(UserEvent::UpdateUser, update_user_handler)
 }

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

@@ -227,6 +227,8 @@ pub enum UserErrorCode {
     EmailInvalid = 20,
     PasswordInvalid = 21,
     UserNameInvalid = 22,
+    UserWorkspaceInvalid = 23,
+    UserIdInvalid = 24,
 }
 
 impl ::protobuf::ProtobufEnum for UserErrorCode {
@@ -248,6 +250,8 @@ impl ::protobuf::ProtobufEnum for UserErrorCode {
             20 => ::std::option::Option::Some(UserErrorCode::EmailInvalid),
             21 => ::std::option::Option::Some(UserErrorCode::PasswordInvalid),
             22 => ::std::option::Option::Some(UserErrorCode::UserNameInvalid),
+            23 => ::std::option::Option::Some(UserErrorCode::UserWorkspaceInvalid),
+            24 => ::std::option::Option::Some(UserErrorCode::UserIdInvalid),
             _ => ::std::option::Option::None
         }
     }
@@ -266,6 +270,8 @@ impl ::protobuf::ProtobufEnum for UserErrorCode {
             UserErrorCode::EmailInvalid,
             UserErrorCode::PasswordInvalid,
             UserErrorCode::UserNameInvalid,
+            UserErrorCode::UserWorkspaceInvalid,
+            UserErrorCode::UserIdInvalid,
         ];
         values
     }
@@ -296,46 +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*\
-    \xa1\x02\n\rUserErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x16\n\x12Databa\
+    \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\x16J\x9c\x05\n\x06\
-    \x12\x04\0\0\x13\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\
-    \x04\x02\0\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\x13\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\x18b\x06proto3\
+    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\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 18 - 13
rust-lib/flowy-user/src/protobuf/model/event.rs

@@ -29,6 +29,7 @@ pub enum UserEvent {
     SignIn = 1,
     SignUp = 2,
     SignOut = 3,
+    UpdateUser = 4,
 }
 
 impl ::protobuf::ProtobufEnum for UserEvent {
@@ -42,6 +43,7 @@ impl ::protobuf::ProtobufEnum for UserEvent {
             1 => ::std::option::Option::Some(UserEvent::SignIn),
             2 => ::std::option::Option::Some(UserEvent::SignUp),
             3 => ::std::option::Option::Some(UserEvent::SignOut),
+            4 => ::std::option::Option::Some(UserEvent::UpdateUser),
             _ => ::std::option::Option::None
         }
     }
@@ -52,6 +54,7 @@ impl ::protobuf::ProtobufEnum for UserEvent {
             UserEvent::SignIn,
             UserEvent::SignUp,
             UserEvent::SignOut,
+            UserEvent::UpdateUser,
         ];
         values
     }
@@ -80,19 +83,21 @@ impl ::protobuf::reflect::ProtobufValue for UserEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*?\n\tUserEvent\x12\r\n\tGetStatus\x10\0\x12\n\n\x06Sig\
-    nIn\x10\x01\x12\n\n\x06SignUp\x10\x02\x12\x0b\n\x07SignOut\x10\x03J\xce\
-    \x01\n\x06\x12\x04\0\0\x07\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
-    \x05\0\x12\x04\x02\0\x07\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x0e\n\
-    \x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x05\0\x02\0\x01\
-    \x12\x03\x03\x04\r\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x10\x11\n\x0b\
-    \n\x04\x05\0\x02\x01\x12\x03\x04\x04\x0f\n\x0c\n\x05\x05\0\x02\x01\x01\
-    \x12\x03\x04\x04\n\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\r\x0e\n\x0b\
-    \n\x04\x05\0\x02\x02\x12\x03\x05\x04\x0f\n\x0c\n\x05\x05\0\x02\x02\x01\
-    \x12\x03\x05\x04\n\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\r\x0e\n\x0b\
-    \n\x04\x05\0\x02\x03\x12\x03\x06\x04\x10\n\x0c\n\x05\x05\0\x02\x03\x01\
-    \x12\x03\x06\x04\x0b\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\x06\x0e\x0fb\
-    \x06proto3\
+    \n\x0bevent.proto*O\n\tUserEvent\x12\r\n\tGetStatus\x10\0\x12\n\n\x06Sig\
+    nIn\x10\x01\x12\n\n\x06SignUp\x10\x02\x12\x0b\n\x07SignOut\x10\x03\x12\
+    \x0e\n\nUpdateUser\x10\x04J\xf7\x01\n\x06\x12\x04\0\0\x08\x01\n\x08\n\
+    \x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x08\x01\n\n\n\x03\
+    \x05\0\x01\x12\x03\x02\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\
+    \x12\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\r\n\x0c\n\x05\x05\0\x02\
+    \0\x02\x12\x03\x03\x10\x11\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x0f\
+    \n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\n\n\x0c\n\x05\x05\0\x02\
+    \x01\x02\x12\x03\x04\r\x0e\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\x0f\
+    \n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\n\n\x0c\n\x05\x05\0\x02\
+    \x02\x02\x12\x03\x05\r\x0e\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\x04\x10\
+    \n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x0b\n\x0c\n\x05\x05\0\x02\
+    \x03\x02\x12\x03\x06\x0e\x0f\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x07\x04\
+    \x13\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x0e\n\x0c\n\x05\x05\0\
+    \x02\x04\x02\x12\x03\x07\x11\x12b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -15,5 +15,8 @@ pub use errors::*;
 mod user_detail; 
 pub use user_detail::*; 
 
+mod user_update; 
+pub use user_update::*; 
+
 mod event; 
 pub use event::*; 

+ 125 - 34
rust-lib/flowy-user/src/protobuf/model/user_detail.rs

@@ -26,9 +26,11 @@
 #[derive(PartialEq,Clone,Default)]
 pub struct UserDetail {
     // message fields
+    pub id: ::std::string::String,
     pub email: ::std::string::String,
     pub name: ::std::string::String,
     pub status: UserStatus,
+    pub workspace: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -45,7 +47,33 @@ impl UserDetail {
         ::std::default::Default::default()
     }
 
-    // string email = 1;
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+
+    // string email = 2;
 
 
     pub fn get_email(&self) -> &str {
@@ -71,7 +99,7 @@ impl UserDetail {
         ::std::mem::replace(&mut self.email, ::std::string::String::new())
     }
 
-    // string name = 2;
+    // string name = 3;
 
 
     pub fn get_name(&self) -> &str {
@@ -97,7 +125,7 @@ impl UserDetail {
         ::std::mem::replace(&mut self.name, ::std::string::String::new())
     }
 
-    // .UserStatus status = 3;
+    // .UserStatus status = 4;
 
 
     pub fn get_status(&self) -> UserStatus {
@@ -111,6 +139,32 @@ impl UserDetail {
     pub fn set_status(&mut self, v: UserStatus) {
         self.status = v;
     }
+
+    // string workspace = 5;
+
+
+    pub fn get_workspace(&self) -> &str {
+        &self.workspace
+    }
+    pub fn clear_workspace(&mut self) {
+        self.workspace.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_workspace(&mut self, v: ::std::string::String) {
+        self.workspace = 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 ::std::string::String {
+        &mut self.workspace
+    }
+
+    // Take field
+    pub fn take_workspace(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.workspace, ::std::string::String::new())
+    }
 }
 
 impl ::protobuf::Message for UserDetail {
@@ -123,13 +177,19 @@ impl ::protobuf::Message for UserDetail {
             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.email)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
                 },
                 2 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.email)?;
                 },
                 3 => {
-                    ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.status, 3, &mut self.unknown_fields)?
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                4 => {
+                    ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.status, 4, &mut self.unknown_fields)?
+                },
+                5 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -143,14 +203,20 @@ impl ::protobuf::Message for UserDetail {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
+        if !self.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
         if !self.email.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.email);
+            my_size += ::protobuf::rt::string_size(2, &self.email);
         }
         if !self.name.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.name);
+            my_size += ::protobuf::rt::string_size(3, &self.name);
         }
         if self.status != UserStatus::Unknown {
-            my_size += ::protobuf::rt::enum_size(3, self.status);
+            my_size += ::protobuf::rt::enum_size(4, self.status);
+        }
+        if !self.workspace.is_empty() {
+            my_size += ::protobuf::rt::string_size(5, &self.workspace);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -158,14 +224,20 @@ impl ::protobuf::Message for UserDetail {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
         if !self.email.is_empty() {
-            os.write_string(1, &self.email)?;
+            os.write_string(2, &self.email)?;
         }
         if !self.name.is_empty() {
-            os.write_string(2, &self.name)?;
+            os.write_string(3, &self.name)?;
         }
         if self.status != UserStatus::Unknown {
-            os.write_enum(3, ::protobuf::ProtobufEnum::value(&self.status))?;
+            os.write_enum(4, ::protobuf::ProtobufEnum::value(&self.status))?;
+        }
+        if !self.workspace.is_empty() {
+            os.write_string(5, &self.workspace)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -205,6 +277,11 @@ impl ::protobuf::Message for UserDetail {
         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>(
+                "id",
+                |m: &UserDetail| { &m.id },
+                |m: &mut UserDetail| { &mut m.id },
+            ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "email",
                 |m: &UserDetail| { &m.email },
@@ -220,6 +297,11 @@ impl ::protobuf::Message for UserDetail {
                 |m: &UserDetail| { &m.status },
                 |m: &mut UserDetail| { &mut m.status },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "workspace",
+                |m: &UserDetail| { &m.workspace },
+                |m: &mut UserDetail| { &mut m.workspace },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<UserDetail>(
                 "UserDetail",
                 fields,
@@ -236,9 +318,11 @@ impl ::protobuf::Message for UserDetail {
 
 impl ::protobuf::Clear for UserDetail {
     fn clear(&mut self) {
+        self.id.clear();
         self.email.clear();
         self.name.clear();
         self.status = UserStatus::Unknown;
+        self.workspace.clear();
         self.unknown_fields.clear();
     }
 }
@@ -309,28 +393,35 @@ impl ::protobuf::reflect::ProtobufValue for UserStatus {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x11user_detail.proto\"[\n\nUserDetail\x12\x14\n\x05email\x18\x01\x20\
-    \x01(\tR\x05email\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12#\n\
-    \x06status\x18\x03\x20\x01(\x0e2\x0b.UserStatusR\x06status*1\n\nUserStat\
-    us\x12\x0b\n\x07Unknown\x10\0\x12\t\n\x05Login\x10\x01\x12\x0b\n\x07Expi\
-    red\x10\x02J\xe2\x02\n\x06\x12\x04\0\0\x0b\x01\n\x08\n\x01\x0c\x12\x03\0\
-    \0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\
-    \x02\x08\x12\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x15\n\x0c\n\x05\x04\
-    \0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\
-    \x0b\x10\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x13\x14\n\x0b\n\x04\x04\
-    \0\x02\x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\
-    \x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\
-    \0\x02\x01\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\
-    \x04\x1a\n\x0c\n\x05\x04\0\x02\x02\x06\x12\x03\x05\x04\x0e\n\x0c\n\x05\
-    \x04\0\x02\x02\x01\x12\x03\x05\x0f\x15\n\x0c\n\x05\x04\0\x02\x02\x03\x12\
-    \x03\x05\x18\x19\n\n\n\x02\x05\0\x12\x04\x07\0\x0b\x01\n\n\n\x03\x05\0\
-    \x01\x12\x03\x07\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\x08\x04\x10\n\
-    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x08\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
-    \x02\x12\x03\x08\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\t\x04\x0e\n\
-    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\t\x04\t\n\x0c\n\x05\x05\0\x02\x01\
-    \x02\x12\x03\t\x0c\r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\n\x04\x10\n\x0c\n\
-    \x05\x05\0\x02\x02\x01\x12\x03\n\x04\x0b\n\x0c\n\x05\x05\0\x02\x02\x02\
-    \x12\x03\n\x0e\x0fb\x06proto3\
+    \n\x11user_detail.proto\"\x89\x01\n\nUserDetail\x12\x0e\n\x02id\x18\x01\
+    \x20\x01(\tR\x02id\x12\x14\n\x05email\x18\x02\x20\x01(\tR\x05email\x12\
+    \x12\n\x04name\x18\x03\x20\x01(\tR\x04name\x12#\n\x06status\x18\x04\x20\
+    \x01(\x0e2\x0b.UserStatusR\x06status\x12\x1c\n\tworkspace\x18\x05\x20\
+    \x01(\tR\tworkspace*1\n\nUserStatus\x12\x0b\n\x07Unknown\x10\0\x12\t\n\
+    \x05Login\x10\x01\x12\x0b\n\x07Expired\x10\x02J\xd0\x03\n\x06\x12\x04\0\
+    \0\r\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\
+    \x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x12\n\x0b\n\x04\x04\0\x02\0\
+    \x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\
+    \n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\0\x02\0\x03\x12\
+    \x03\x03\x10\x11\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x15\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\x10\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x13\x14\n\
+    \x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x02\
+    \x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x05\x0b\x0f\
+    \n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\n\x04\x04\0\x02\
+    \x03\x12\x03\x06\x04\x1a\n\x0c\n\x05\x04\0\x02\x03\x06\x12\x03\x06\x04\
+    \x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0f\x15\n\x0c\n\x05\x04\0\
+    \x02\x03\x03\x12\x03\x06\x18\x19\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x07\
+    \x04\x19\n\x0c\n\x05\x04\0\x02\x04\x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\
+    \0\x02\x04\x01\x12\x03\x07\x0b\x14\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\
+    \x07\x17\x18\n\n\n\x02\x05\0\x12\x04\t\0\r\x01\n\n\n\x03\x05\0\x01\x12\
+    \x03\t\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\n\x04\x10\n\x0c\n\x05\x05\
+    \0\x02\0\x01\x12\x03\n\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\n\x0e\
+    \x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x0b\x04\x0e\n\x0c\n\x05\x05\0\x02\
+    \x01\x01\x12\x03\x0b\x04\t\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x0b\x0c\
+    \r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x0c\x04\x10\n\x0c\n\x05\x05\0\x02\
+    \x02\x01\x12\x03\x0c\x04\x0b\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x0c\
+    \x0e\x0fb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 549 - 0
rust-lib/flowy-user/src/protobuf/model/user_update.rs

@@ -0,0 +1,549 @@
+// 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 `user_update.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 UpdateUserRequest {
+    // message fields
+    pub id: ::std::string::String,
+    // message oneof groups
+    pub one_of_name: ::std::option::Option<UpdateUserRequest_oneof_one_of_name>,
+    pub one_of_email: ::std::option::Option<UpdateUserRequest_oneof_one_of_email>,
+    pub one_of_workspace: ::std::option::Option<UpdateUserRequest_oneof_one_of_workspace>,
+    pub one_of_password: ::std::option::Option<UpdateUserRequest_oneof_one_of_password>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UpdateUserRequest {
+    fn default() -> &'a UpdateUserRequest {
+        <UpdateUserRequest as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserRequest_oneof_one_of_name {
+    name(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserRequest_oneof_one_of_email {
+    email(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserRequest_oneof_one_of_workspace {
+    workspace(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserRequest_oneof_one_of_password {
+    password(::std::string::String),
+}
+
+impl UpdateUserRequest {
+    pub fn new() -> UpdateUserRequest {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+
+    // string name = 2;
+
+
+    pub fn get_name(&self) -> &str {
+        match self.one_of_name {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_name(&mut self) {
+        self.one_of_name = ::std::option::Option::None;
+    }
+
+    pub fn has_name(&self) -> bool {
+        match self.one_of_name {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.one_of_name = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(_)) = self.one_of_name {
+        } else {
+            self.one_of_name = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(::std::string::String::new()));
+        }
+        match self.one_of_name {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        if self.has_name() {
+            match self.one_of_name.take() {
+                ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // string email = 3;
+
+
+    pub fn get_email(&self) -> &str {
+        match self.one_of_email {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_email(&mut self) {
+        self.one_of_email = ::std::option::Option::None;
+    }
+
+    pub fn has_email(&self) -> bool {
+        match self.one_of_email {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_email(&mut self, v: ::std::string::String) {
+        self.one_of_email = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_email(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(_)) = self.one_of_email {
+        } else {
+            self.one_of_email = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(::std::string::String::new()));
+        }
+        match self.one_of_email {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_email(&mut self) -> ::std::string::String {
+        if self.has_email() {
+            match self.one_of_email.take() {
+                ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // string workspace = 4;
+
+
+    pub fn get_workspace(&self) -> &str {
+        match self.one_of_workspace {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_workspace(&mut self) {
+        self.one_of_workspace = ::std::option::Option::None;
+    }
+
+    pub fn has_workspace(&self) -> bool {
+        match self.one_of_workspace {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_workspace(&mut self, v: ::std::string::String) {
+        self.one_of_workspace = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_workspace(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(_)) = self.one_of_workspace {
+        } else {
+            self.one_of_workspace = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(::std::string::String::new()));
+        }
+        match self.one_of_workspace {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_workspace(&mut self) -> ::std::string::String {
+        if self.has_workspace() {
+            match self.one_of_workspace.take() {
+                ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // string password = 5;
+
+
+    pub fn get_password(&self) -> &str {
+        match self.one_of_password {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_password(&mut self) {
+        self.one_of_password = ::std::option::Option::None;
+    }
+
+    pub fn has_password(&self) -> bool {
+        match self.one_of_password {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_password(&mut self, v: ::std::string::String) {
+        self.one_of_password = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_password(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(_)) = self.one_of_password {
+        } else {
+            self.one_of_password = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(::std::string::String::new()));
+        }
+        match self.one_of_password {
+            ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_password(&mut self) -> ::std::string::String {
+        if self.has_password() {
+            match self.one_of_password.take() {
+                ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for UpdateUserRequest {
+    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.id)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_name = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_name::name(is.read_string()?));
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_email = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_email::email(is.read_string()?));
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_workspace = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_workspace::workspace(is.read_string()?));
+                },
+                5 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_password = ::std::option::Option::Some(UpdateUserRequest_oneof_one_of_password::password(is.read_string()?));
+                },
+                _ => {
+                    ::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.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_name {
+            match v {
+                &UpdateUserRequest_oneof_one_of_name::name(ref v) => {
+                    my_size += ::protobuf::rt::string_size(2, &v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_email {
+            match v {
+                &UpdateUserRequest_oneof_one_of_email::email(ref v) => {
+                    my_size += ::protobuf::rt::string_size(3, &v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_workspace {
+            match v {
+                &UpdateUserRequest_oneof_one_of_workspace::workspace(ref v) => {
+                    my_size += ::protobuf::rt::string_size(4, &v);
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_password {
+            match v {
+                &UpdateUserRequest_oneof_one_of_password::password(ref v) => {
+                    my_size += ::protobuf::rt::string_size(5, &v);
+                },
+            };
+        }
+        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.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_name {
+            match v {
+                &UpdateUserRequest_oneof_one_of_name::name(ref v) => {
+                    os.write_string(2, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_email {
+            match v {
+                &UpdateUserRequest_oneof_one_of_email::email(ref v) => {
+                    os.write_string(3, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_workspace {
+            match v {
+                &UpdateUserRequest_oneof_one_of_workspace::workspace(ref v) => {
+                    os.write_string(4, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_password {
+            match v {
+                &UpdateUserRequest_oneof_one_of_password::password(ref v) => {
+                    os.write_string(5, v)?;
+                },
+            };
+        }
+        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() -> UpdateUserRequest {
+        UpdateUserRequest::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>(
+                "id",
+                |m: &UpdateUserRequest| { &m.id },
+                |m: &mut UpdateUserRequest| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "name",
+                UpdateUserRequest::has_name,
+                UpdateUserRequest::get_name,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "email",
+                UpdateUserRequest::has_email,
+                UpdateUserRequest::get_email,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "workspace",
+                UpdateUserRequest::has_workspace,
+                UpdateUserRequest::get_workspace,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "password",
+                UpdateUserRequest::has_password,
+                UpdateUserRequest::get_password,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdateUserRequest>(
+                "UpdateUserRequest",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UpdateUserRequest {
+        static instance: ::protobuf::rt::LazyV2<UpdateUserRequest> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UpdateUserRequest::new)
+    }
+}
+
+impl ::protobuf::Clear for UpdateUserRequest {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.one_of_name = ::std::option::Option::None;
+        self.one_of_email = ::std::option::Option::None;
+        self.one_of_workspace = ::std::option::Option::None;
+        self.one_of_password = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for UpdateUserRequest {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for UpdateUserRequest {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x11user_update.proto\"\xd5\x01\n\x11UpdateUserRequest\x12\x0e\n\x02id\
+    \x18\x01\x20\x01(\tR\x02id\x12\x14\n\x04name\x18\x02\x20\x01(\tH\0R\x04n\
+    ame\x12\x16\n\x05email\x18\x03\x20\x01(\tH\x01R\x05email\x12\x1e\n\twork\
+    space\x18\x04\x20\x01(\tH\x02R\tworkspace\x12\x1c\n\x08password\x18\x05\
+    \x20\x01(\tH\x03R\x08passwordB\r\n\x0bone_of_nameB\x0e\n\x0cone_of_email\
+    B\x12\n\x10one_of_workspaceB\x11\n\x0fone_of_passwordJ\xa9\x03\n\x06\x12\
+    \x04\0\0\x08\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\
+    \x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x19\n\x0b\n\x04\x04\0\
+    \x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\
+    \n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\0\x02\0\
+    \x03\x12\x03\x03\x10\x11\n\x0b\n\x04\x04\0\x08\0\x12\x03\x04\x04*\n\x0c\
+    \n\x05\x04\0\x08\0\x01\x12\x03\x04\n\x15\n\x0b\n\x04\x04\0\x02\x01\x12\
+    \x03\x04\x18(\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x18\x1e\n\x0c\n\
+    \x05\x04\0\x02\x01\x01\x12\x03\x04\x1f#\n\x0c\n\x05\x04\0\x02\x01\x03\
+    \x12\x03\x04&'\n\x0b\n\x04\x04\0\x08\x01\x12\x03\x05\x04,\n\x0c\n\x05\
+    \x04\0\x08\x01\x01\x12\x03\x05\n\x16\n\x0b\n\x04\x04\0\x02\x02\x12\x03\
+    \x05\x19*\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x05\x19\x1f\n\x0c\n\x05\
+    \x04\0\x02\x02\x01\x12\x03\x05\x20%\n\x0c\n\x05\x04\0\x02\x02\x03\x12\
+    \x03\x05()\n\x0b\n\x04\x04\0\x08\x02\x12\x03\x06\x044\n\x0c\n\x05\x04\0\
+    \x08\x02\x01\x12\x03\x06\n\x1a\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x1d\
+    2\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\x06\x1d#\n\x0c\n\x05\x04\0\x02\
+    \x03\x01\x12\x03\x06$-\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x0601\n\x0b\
+    \n\x04\x04\0\x08\x03\x12\x03\x07\x042\n\x0c\n\x05\x04\0\x08\x03\x01\x12\
+    \x03\x07\n\x19\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x07\x1c0\n\x0c\n\x05\
+    \x04\0\x02\x04\x05\x12\x03\x07\x1c\"\n\x0c\n\x05\x04\0\x02\x04\x01\x12\
+    \x03\x07#+\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x07./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()
+    })
+}

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

@@ -17,4 +17,6 @@ enum UserErrorCode {
     EmailInvalid = 20;
     PasswordInvalid = 21;
     UserNameInvalid = 22;
+    UserWorkspaceInvalid = 23;
+    UserIdInvalid = 24;
 }

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

@@ -5,4 +5,5 @@ enum UserEvent {
     SignIn = 1;
     SignUp = 2;
     SignOut = 3;
+    UpdateUser = 4;
 }

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

@@ -1,9 +1,11 @@
 syntax = "proto3";
 
 message UserDetail {
-    string email = 1;
-    string name = 2;
-    UserStatus status = 3;
+    string id = 1;
+    string email = 2;
+    string name = 3;
+    UserStatus status = 4;
+    string workspace = 5;
 }
 enum UserStatus {
     Unknown = 0;

+ 9 - 0
rust-lib/flowy-user/src/protobuf/proto/user_update.proto

@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+message UpdateUserRequest {
+    string id = 1;
+    oneof one_of_name { string name = 2; };
+    oneof one_of_email { string email = 3; };
+    oneof one_of_workspace { string workspace = 4; };
+    oneof one_of_password { string password = 5; };
+}

+ 7 - 4
rust-lib/flowy-user/src/services/user_session/builder.rs

@@ -1,4 +1,4 @@
-use crate::services::user_session::{user_server::MockUserServer, UserSession, UserSessionConfig};
+use crate::services::user_session::{user_server::UserServer, UserSession, UserSessionConfig};
 
 pub struct UserSessionBuilder {
     config: Option<UserSessionConfig>,
@@ -12,9 +12,12 @@ impl UserSessionBuilder {
         self
     }
 
-    pub fn build(mut self) -> UserSession {
+    pub fn build<S>(mut self, server: S) -> UserSession
+    where
+        S: 'static + UserServer + Send + Sync,
+    {
         let config = self.config.take().unwrap();
-        let register = MockUserServer {};
-        UserSession::new(config, register)
+
+        UserSession::new(config, server)
     }
 }

+ 1 - 0
rust-lib/flowy-user/src/services/user_session/mod.rs

@@ -1,4 +1,5 @@
 pub use builder::*;
+pub use user_server::*;
 pub use user_session::*;
 
 mod builder;

+ 1 - 33
rust-lib/flowy-user/src/services/user_session/user_server.rs

@@ -3,6 +3,7 @@ use crate::{
     errors::{ErrorBuilder, UserError, UserErrorCode},
     sql_tables::User,
 };
+use flowy_infra::uuid;
 
 pub trait UserServer {
     fn sign_up(&self, params: SignUpParams) -> Result<User, UserError>;
@@ -10,36 +11,3 @@ pub trait UserServer {
     fn get_user_info(&self, user_id: &str) -> Result<UserDetail, UserError>;
     fn sign_out(&self, user_id: &str) -> Result<(), UserError>;
 }
-
-pub struct MockUserServer {}
-
-impl UserServer for MockUserServer {
-    fn sign_up(&self, params: SignUpParams) -> Result<User, UserError> {
-        let user_id = "9527".to_owned();
-        // let user_id = uuid();
-        Ok(User::new(
-            user_id,
-            params.name,
-            params.email,
-            params.password,
-        ))
-    }
-
-    fn sign_in(&self, params: SignInParams) -> Result<User, UserError> {
-        let user_id = "9527".to_owned();
-        Ok(User::new(
-            user_id,
-            "".to_owned(),
-            params.email,
-            params.password,
-        ))
-    }
-
-    fn get_user_info(&self, _user_id: &str) -> Result<UserDetail, UserError> {
-        Err(ErrorBuilder::new(UserErrorCode::Unknown).build())
-    }
-
-    fn sign_out(&self, _user_id: &str) -> Result<(), UserError> {
-        Err(ErrorBuilder::new(UserErrorCode::Unknown).build())
-    }
-}

+ 40 - 15
rust-lib/flowy-user/src/services/user_session/user_session.rs

@@ -10,13 +10,10 @@ use lazy_static::lazy_static;
 use std::sync::RwLock;
 
 use crate::{
-    entities::{SignInParams, SignUpParams, UserDetail},
+    entities::{SignInParams, SignUpParams, UpdateUserParams, UserDetail},
     errors::{ErrorBuilder, UserError, UserErrorCode},
-    services::user_session::{
-        database::UserDB,
-        user_server::{UserServer, *},
-    },
-    sql_tables::User,
+    services::user_session::{database::UserDB, user_server::UserServer},
+    sql_tables::{User, UserChangeset},
 };
 
 pub struct UserSessionConfig {
@@ -38,7 +35,7 @@ pub struct UserSession {
 }
 
 impl UserSession {
-    pub fn new<R>(config: UserSessionConfig, register: R) -> Self
+    pub fn new<R>(config: UserSessionConfig, server: R) -> Self
     where
         R: 'static + UserServer + Send + Sync,
     {
@@ -46,7 +43,7 @@ impl UserSession {
         Self {
             database: db,
             config,
-            server: Box::new(register),
+            server: Box::new(server),
         }
     }
 
@@ -77,7 +74,16 @@ impl UserSession {
         Ok(())
     }
 
-    pub async fn current_user_detail(&self) -> Result<UserDetail, UserError> {
+    pub async fn update_user(&self, params: UpdateUserParams) -> Result<UserDetail, UserError> {
+        let changeset = UserChangeset::new(params);
+        let conn = self.get_db_connection()?;
+        diesel_update_table!(user_table, changeset, conn);
+
+        let user_detail = self.current_user_detail()?;
+        Ok(user_detail)
+    }
+
+    pub fn current_user_detail(&self) -> Result<UserDetail, UserError> {
         let user_id = self.current_user_id()?;
         let conn = self.get_db_connection()?;
 
@@ -98,10 +104,21 @@ impl UserSession {
     }
 
     pub fn get_db_connection(&self) -> Result<DBConnection, UserError> {
-        match get_current_user_id()? {
-            None => Err(ErrorBuilder::new(UserErrorCode::UserNotLoginYet).build()),
-            Some(user_id) => self.database.get_connection(&user_id),
-        }
+        let user_id = get_current_user_id()?;
+        self.database.get_connection(&user_id)
+    }
+
+    #[allow(dead_code)]
+    pub fn get_current_workspace(&self) -> Result<String, UserError> {
+        let user_id = get_current_user_id()?;
+        let conn = self.get_db_connection()?;
+
+        let workspace = dsl::user_table
+            .filter(user_table::id.eq(&user_id))
+            .select(user_table::workspace)
+            .first::<String>(&*conn)?;
+
+        Ok(workspace)
     }
 }
 
@@ -133,7 +150,8 @@ const USER_ID_DISK_CACHE_KEY: &str = "user_id";
 lazy_static! {
     pub static ref CURRENT_USER_ID: RwLock<Option<String>> = RwLock::new(None);
 }
-pub(crate) fn get_current_user_id() -> Result<Option<String>, UserError> {
+
+pub(crate) fn get_current_user_id() -> Result<String, UserError> {
     let read_guard = CURRENT_USER_ID.read().map_err(|e| {
         ErrorBuilder::new(UserErrorCode::ReadCurrentIdFailed)
             .error(e)
@@ -149,7 +167,14 @@ pub(crate) fn get_current_user_id() -> Result<Option<String>, UserError> {
         *(CURRENT_USER_ID.write().unwrap()) = user_id.clone();
     }
 
-    Ok(user_id)
+    if user_id.is_none() {
+        return Err(ErrorBuilder::new(UserErrorCode::UserNotLoginYet).build());
+    }
+
+    match user_id {
+        None => Err(ErrorBuilder::new(UserErrorCode::UserNotLoginYet).build()),
+        Some(user_id) => Ok(user_id),
+    }
 }
 
 pub(crate) fn set_current_user_id(user_id: Option<String>) -> Result<(), UserError> {

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

@@ -1,3 +1,3 @@
-mod user_table;
+mod user;
 
-pub use user_table::*;
+pub use user::*;

+ 51 - 0
rust-lib/flowy-user/src/sql_tables/user.rs

@@ -0,0 +1,51 @@
+use crate::entities::UpdateUserParams;
+use flowy_database::schema::user_table;
+
+#[derive(Clone, Default, Queryable, Identifiable, Insertable)]
+#[table_name = "user_table"]
+pub struct User {
+    pub(crate) id: String,
+    pub(crate) name: String,
+    pub(crate) password: String,
+    pub(crate) email: String,
+    pub(crate) workspace: String,
+}
+
+impl User {
+    pub fn new(id: String, name: String, email: String, password: String) -> Self {
+        Self {
+            id,
+            name,
+            email,
+            password,
+            workspace: "".to_owned(),
+        }
+    }
+
+    pub fn set_workspace(mut self, workspace: String) -> Self {
+        self.workspace = workspace;
+        self
+    }
+}
+
+#[derive(AsChangeset, Identifiable, Default, Debug)]
+#[table_name = "user_table"]
+pub struct UserChangeset {
+    pub id: String,
+    pub workspace: Option<String>,
+    pub name: Option<String>,
+    pub email: Option<String>,
+    pub password: Option<String>,
+}
+
+impl UserChangeset {
+    pub fn new(params: UpdateUserParams) -> Self {
+        UserChangeset {
+            id: params.id,
+            workspace: params.workspace,
+            name: params.name,
+            email: params.email,
+            password: params.password,
+        }
+    }
+}

+ 0 - 21
rust-lib/flowy-user/src/sql_tables/user_table.rs

@@ -1,21 +0,0 @@
-use flowy_database::schema::user_table;
-
-#[derive(Clone, Default, Queryable, Identifiable, Insertable)]
-#[table_name = "user_table"]
-pub struct User {
-    pub(crate) id: String,
-    pub(crate) name: String,
-    password: String,
-    pub(crate) email: String,
-}
-
-impl User {
-    pub fn new(id: String, name: String, email: String, password: String) -> Self {
-        Self {
-            id,
-            name,
-            email,
-            password,
-        }
-    }
-}

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

@@ -1,7 +1,7 @@
-use flowy_test::EventTester;
+use flowy_test::{EventTester, TesterConfig};
 use flowy_user::errors::UserError;
 
-pub type UserEventTester = EventTester<UserError>;
+pub type UserEventTester = EventTester<UserError, TesterConfig>;
 
 pub(crate) fn invalid_email_test_case() -> Vec<String> {
     // https://gist.github.com/cjaoude/fd9910626629b53c4d25

+ 2 - 6
rust-lib/flowy-user/tests/event/sign_in_test.rs

@@ -1,10 +1,6 @@
 use crate::helper::*;
-use flowy_test::prelude::*;
-use flowy_user::{
-    errors::{UserError, UserErrorCode},
-    event::UserEvent::*,
-    prelude::*,
-};
+
+use flowy_user::{errors::UserErrorCode, event::UserEvent::*, prelude::*};
 use serial_test::*;
 
 #[test]

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

@@ -1,5 +1,5 @@
 use crate::helper::*;
-use flowy_test::prelude::*;
+
 use flowy_user::{errors::*, event::UserEvent::*, prelude::*};
 use serial_test::*;
 

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

@@ -1,7 +1,8 @@
 use crate::helper::*;
-use flowy_test::prelude::*;
-use flowy_user::{event::UserEvent::*, prelude::*};
+
+use flowy_user::{errors::UserErrorCode, event::UserEvent::*, prelude::*};
 use serial_test::*;
+
 #[test]
 #[should_panic]
 #[serial]
@@ -31,3 +32,41 @@ fn user_status_did_found_after_login() {
         .sync_send()
         .parse::<UserDetail>();
 }
+
+#[test]
+#[serial]
+fn user_update_with_invalid_email() {
+    let _ = UserEventTester::new(SignOut).sync_send();
+    let request = SignInRequest {
+        email: valid_email(),
+        password: valid_password(),
+    };
+
+    let _ = UserEventTester::new(SignIn)
+        .request(request)
+        .sync_send()
+        .parse::<UserDetail>();
+
+    let user_detail = UserEventTester::new(GetStatus)
+        .sync_send()
+        .parse::<UserDetail>();
+
+    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!(
+            UserEventTester::new(UpdateUser)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
+            UserErrorCode::EmailInvalid
+        );
+    }
+}

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

@@ -21,6 +21,7 @@ lazy_static = "1.4.0"
 serde = { version = "1.0", features = ["derive"] }
 derive_more = {version = "0.99", features = ["display"]}
 bincode = { version = "1.3"}
+unicode-segmentation = "1.7.1"
 
 [dev-dependencies]
 flowy-test = { path = "../flowy-test" }

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

@@ -1,7 +1,7 @@
 use crate::{
     entities::{
-        app::{app_color_style::AppColorStyle, app_name::AppName},
-        workspace::WorkspaceId,
+        app::parser::{AppColorStyle, AppName},
+        workspace::parser::WorkspaceId,
     },
     errors::*,
 };
@@ -66,3 +66,18 @@ impl TryInto<CreateAppParams> for CreateAppRequest {
         })
     }
 }
+
+#[derive(ProtoBuf, Default, Debug)]
+pub struct AppDetail {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2)]
+    pub workspace_id: String,
+
+    #[pb(index = 3)]
+    pub name: String,
+
+    #[pb(index = 4)]
+    pub desc: String,
+}

+ 5 - 2
rust-lib/flowy-workspace/src/entities/app/app_update.rs

@@ -1,7 +1,10 @@
 use crate::{
     entities::{
-        app::{app_color_style::AppColorStyle, app_id::AppId, app_name::AppName, ColorStyle},
-        workspace::WorkspaceId,
+        app::{
+            parser::{AppColorStyle, AppId, AppName},
+            ColorStyle,
+        },
+        workspace::parser::WorkspaceId,
     },
     errors::{ErrorBuilder, WorkspaceError, WorkspaceErrorCode},
 };

+ 4 - 6
rust-lib/flowy-workspace/src/entities/app/mod.rs

@@ -1,8 +1,6 @@
-mod app_color_style;
-mod app_create;
-mod app_id;
-mod app_name;
-mod app_update;
-
 pub use app_create::*;
 pub use app_update::*;
+
+mod app_create;
+mod app_update;
+mod parser;

+ 0 - 0
rust-lib/flowy-workspace/src/entities/app/app_color_style.rs → rust-lib/flowy-workspace/src/entities/app/parser/app_color_style.rs


+ 0 - 0
rust-lib/flowy-workspace/src/entities/app/app_id.rs → rust-lib/flowy-workspace/src/entities/app/parser/app_id.rs


+ 0 - 0
rust-lib/flowy-workspace/src/entities/app/app_name.rs → rust-lib/flowy-workspace/src/entities/app/parser/app_name.rs


+ 7 - 0
rust-lib/flowy-workspace/src/entities/app/parser/mod.rs

@@ -0,0 +1,7 @@
+mod app_color_style;
+mod app_id;
+mod app_name;
+
+pub use app_color_style::*;
+pub use app_id::*;
+pub use app_name::*;

+ 5 - 8
rust-lib/flowy-workspace/src/entities/workspace/mod.rs

@@ -1,11 +1,8 @@
-mod workspace_create;
-mod workspace_id;
-mod workspace_name;
-mod workspace_query;
-mod workspace_update;
-
 pub use workspace_create::*;
-pub use workspace_id::*;
-pub use workspace_name::*;
 pub use workspace_query::*;
 pub use workspace_update::*;
+
+pub(crate) mod parser;
+mod workspace_create;
+mod workspace_query;
+mod workspace_update;

+ 5 - 0
rust-lib/flowy-workspace/src/entities/workspace/parser/mod.rs

@@ -0,0 +1,5 @@
+mod workspace_id;
+mod workspace_name;
+
+pub use workspace_id::*;
+pub use workspace_name::*;

+ 0 - 0
rust-lib/flowy-workspace/src/entities/workspace/workspace_id.rs → rust-lib/flowy-workspace/src/entities/workspace/parser/workspace_id.rs


+ 6 - 0
rust-lib/flowy-workspace/src/entities/workspace/workspace_name.rs → rust-lib/flowy-workspace/src/entities/workspace/parser/workspace_name.rs

@@ -1,3 +1,5 @@
+use unicode_segmentation::UnicodeSegmentation;
+
 #[derive(Debug)]
 pub struct WorkspaceName(pub String);
 
@@ -7,6 +9,10 @@ impl WorkspaceName {
             return Err(format!("Workspace name can not be empty or whitespace"));
         }
 
+        if s.graphemes(true).count() > 256 {
+            return Err(format!("Workspace name too long"));
+        }
+
         Ok(Self(s))
     }
 }

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

@@ -1,4 +1,4 @@
-use crate::{entities::workspace::workspace_name::WorkspaceName, errors::*};
+use crate::{entities::workspace::parser::*, errors::*};
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
 

+ 4 - 5
rust-lib/flowy-workspace/src/entities/workspace/workspace_update.rs

@@ -1,10 +1,9 @@
-use crate::{
-    entities::workspace::{workspace_id::WorkspaceId, workspace_name::WorkspaceName},
-    errors::*,
-};
-use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
 
+use flowy_derive::ProtoBuf;
+
+use crate::{entities::workspace::parser::*, errors::*};
+
 #[derive(ProtoBuf, Default)]
 pub struct UpdateWorkspaceRequest {
     #[pb(index = 1)]

+ 3 - 0
rust-lib/flowy-workspace/src/errors.rs

@@ -43,6 +43,9 @@ pub enum WorkspaceErrorCode {
 
     #[display(fmt = "Database internal error")]
     DatabaseInternalError = 6,
+
+    #[display(fmt = "User internal error")]
+    UserInternalError    = 10,
 }
 
 impl std::default::Default for WorkspaceErrorCode {

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

@@ -0,0 +1,16 @@
+use crate::{
+    entities::app::{AppDetail, CreateAppParams, CreateAppRequest},
+    errors::WorkspaceError,
+    services::AppController,
+};
+use flowy_dispatch::prelude::{response_ok, Data, ModuleData, ResponseResult};
+use std::{convert::TryInto, sync::Arc};
+
+pub async fn create_app(
+    data: Data<CreateAppRequest>,
+    controller: ModuleData<Arc<AppController>>,
+) -> ResponseResult<AppDetail, WorkspaceError> {
+    let params: CreateAppParams = data.into_inner().try_into()?;
+    let detail = controller.save_app(params)?;
+    response_ok(detail)
+}

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

@@ -1,3 +1,5 @@
+mod app_handler;
 mod workspace_handler;
 
+pub use app_handler::*;
 pub use workspace_handler::*;

+ 3 - 3
rust-lib/flowy-workspace/src/macros.rs

@@ -10,9 +10,9 @@ macro_rules! impl_sql_binary_expression {
             ) -> diesel::serialize::Result {
                 let bytes: Vec<u8> = self.try_into().map_err(|e| format!("{:?}", e))?;
                 diesel::serialize::ToSql::<
-                                                        diesel::sql_types::Binary,
-                                                        diesel::sqlite::Sqlite,
-                                                    >::to_sql(&bytes, out)
+                                                                        diesel::sql_types::Binary,
+                                                                        diesel::sqlite::Sqlite,
+                                                                    >::to_sql(&bytes, out)
 
                 // match self.try_into() {
                 //     Ok(bytes) => diesel::serialize::ToSql::<

+ 19 - 3
rust-lib/flowy-workspace/src/module.rs

@@ -1,11 +1,27 @@
 use flowy_dispatch::prelude::*;
 
-use crate::{event::WorkspaceEvent, handlers::create_workspace, services::WorkspaceController};
+use crate::{
+    errors::WorkspaceError,
+    event::WorkspaceEvent,
+    handlers::create_workspace,
+    services::{AppController, WorkspaceController},
+};
+use flowy_database::{DBConnection, UserDatabaseConnection};
 use std::sync::Arc;
 
-pub fn create(controller: Arc<WorkspaceController>) -> Module {
+pub trait WorkspaceUser: Send + Sync {
+    fn set_current_workspace(&self, id: &str);
+    fn get_current_workspace(&self) -> Result<String, WorkspaceError>;
+    fn db_connection(&self) -> Result<DBConnection, WorkspaceError>;
+}
+
+pub fn create(user: Arc<dyn WorkspaceUser>) -> Module {
+    let workspace_controller = Arc::new(WorkspaceController::new(user.clone()));
+    let app_controller = Arc::new(AppController::new(user.clone()));
+
     Module::new()
         .name("Flowy-Workspace")
-        .data(controller)
+        .data(workspace_controller)
+        .data(app_controller)
         .event(WorkspaceEvent::CreateWorkspace, create_workspace)
 }

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

@@ -482,21 +482,309 @@ impl ::protobuf::reflect::ProtobufValue for ColorStyle {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct AppDetail {
+    // message fields
+    pub id: ::std::string::String,
+    pub workspace_id: ::std::string::String,
+    pub name: ::std::string::String,
+    pub desc: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a AppDetail {
+    fn default() -> &'a AppDetail {
+        <AppDetail as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl AppDetail {
+    pub fn new() -> AppDetail {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::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())
+    }
+
+    // string name = 3;
+
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+    pub fn clear_name(&mut self) {
+        self.name.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.name = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        &mut self.name
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.name, ::std::string::String::new())
+    }
+
+    // string desc = 4;
+
+
+    pub fn get_desc(&self) -> &str {
+        &self.desc
+    }
+    pub fn clear_desc(&mut self) {
+        self.desc.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_desc(&mut self, v: ::std::string::String) {
+        self.desc = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_desc(&mut self) -> &mut ::std::string::String {
+        &mut self.desc
+    }
+
+    // Take field
+    pub fn take_desc(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.desc, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for AppDetail {
+    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.id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace_id)?;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                4 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.desc)?;
+                },
+                _ => {
+                    ::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.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if !self.workspace_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.workspace_id);
+        }
+        if !self.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(3, &self.name);
+        }
+        if !self.desc.is_empty() {
+            my_size += ::protobuf::rt::string_size(4, &self.desc);
+        }
+        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.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        if !self.workspace_id.is_empty() {
+            os.write_string(2, &self.workspace_id)?;
+        }
+        if !self.name.is_empty() {
+            os.write_string(3, &self.name)?;
+        }
+        if !self.desc.is_empty() {
+            os.write_string(4, &self.desc)?;
+        }
+        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() -> AppDetail {
+        AppDetail::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>(
+                "id",
+                |m: &AppDetail| { &m.id },
+                |m: &mut AppDetail| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "workspace_id",
+                |m: &AppDetail| { &m.workspace_id },
+                |m: &mut AppDetail| { &mut m.workspace_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "name",
+                |m: &AppDetail| { &m.name },
+                |m: &mut AppDetail| { &mut m.name },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "desc",
+                |m: &AppDetail| { &m.desc },
+                |m: &mut AppDetail| { &mut m.desc },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<AppDetail>(
+                "AppDetail",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static AppDetail {
+        static instance: ::protobuf::rt::LazyV2<AppDetail> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(AppDetail::new)
+    }
+}
+
+impl ::protobuf::Clear for AppDetail {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.workspace_id.clear();
+        self.name.clear();
+        self.desc.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for AppDetail {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for AppDetail {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x10app_create.proto\"\x8b\x01\n\x10CreateAppRequest\x12!\n\x0cworkspa\
     ce_id\x18\x01\x20\x01(\tR\x0bworkspaceId\x12\x12\n\x04name\x18\x02\x20\
     \x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12,\n\
     \x0bcolor_style\x18\x04\x20\x01(\x0b2\x0b.ColorStyleR\ncolorStyle\"-\n\n\
-    ColorStyle\x12\x1f\n\x0btheme_color\x18\x01\x20\x01(\tR\nthemeColorJ\xd5\
-    \x02\n\x06\x12\x04\0\0\n\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
-    \x04\0\x12\x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x18\n\
-    \x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x1c\n\x0c\n\x05\x04\0\x02\0\x05\
-    \x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x17\n\x0c\
-    \n\x05\x04\0\x02\0\x03\x12\x03\x03\x1a\x1b\n\x0b\n\x04\x04\0\x02\x01\x12\
-    \x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\
-    \x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\
-    \x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\
-    \n\x05\x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\
+    ColorStyle\x12\x1f\n\x0btheme_color\x18\x01\x20\x01(\tR\nthemeColor\"f\n\
+    \tAppDetail\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12!\n\x0cworkspac\
+    e_id\x18\x02\x20\x01(\tR\x0bworkspaceId\x12\x12\n\x04name\x18\x03\x20\
+    \x01(\tR\x04name\x12\x12\n\x04desc\x18\x04\x20\x01(\tR\x04descJ\xc9\x04\
+    \n\x06\x12\x04\0\0\x10\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\
+    \0\x12\x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x18\n\x0b\n\
+    \x04\x04\0\x02\0\x12\x03\x03\x04\x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\
+    \x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x17\n\x0c\n\x05\
+    \x04\0\x02\0\x03\x12\x03\x03\x1a\x1b\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
+    \x04\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\
+    \x04\0\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
+    \x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\
+    \x05\x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\
     \x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\
     \x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x04\x1f\n\x0c\n\x05\x04\0\x02\x03\
     \x06\x12\x03\x06\x04\x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0f\
@@ -504,7 +792,19 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x12\x04\x08\0\n\x01\n\n\n\x03\x04\x01\x01\x12\x03\x08\x08\x12\n\x0b\n\
     \x04\x04\x01\x02\0\x12\x03\t\x04\x1b\n\x0c\n\x05\x04\x01\x02\0\x05\x12\
     \x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\t\x0b\x16\n\x0c\n\x05\
-    \x04\x01\x02\0\x03\x12\x03\t\x19\x1ab\x06proto3\
+    \x04\x01\x02\0\x03\x12\x03\t\x19\x1a\n\n\n\x02\x04\x02\x12\x04\x0b\0\x10\
+    \x01\n\n\n\x03\x04\x02\x01\x12\x03\x0b\x08\x11\n\x0b\n\x04\x04\x02\x02\0\
+    \x12\x03\x0c\x04\x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0c\x04\n\n\
+    \x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0c\x0b\r\n\x0c\n\x05\x04\x02\x02\0\
+    \x03\x12\x03\x0c\x10\x11\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\r\x04\x1c\n\
+    \x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x02\x02\
+    \x01\x01\x12\x03\r\x0b\x17\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\r\x1a\
+    \x1b\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x0e\x04\x14\n\x0c\n\x05\x04\x02\
+    \x02\x02\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\
+    \x0e\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\x0e\x12\x13\n\x0b\n\
+    \x04\x04\x02\x02\x03\x12\x03\x0f\x04\x14\n\x0c\n\x05\x04\x02\x02\x03\x05\
+    \x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\x0f\x0b\x0f\n\
+    \x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x0f\x12\x13b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -222,6 +222,7 @@ pub enum WorkspaceErrorCode {
     AppIdInvalid = 4,
     DatabaseConnectionFail = 5,
     DatabaseInternalError = 6,
+    UserInternalError = 10,
 }
 
 impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
@@ -238,6 +239,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
             4 => ::std::option::Option::Some(WorkspaceErrorCode::AppIdInvalid),
             5 => ::std::option::Option::Some(WorkspaceErrorCode::DatabaseConnectionFail),
             6 => ::std::option::Option::Some(WorkspaceErrorCode::DatabaseInternalError),
+            10 => ::std::option::Option::Some(WorkspaceErrorCode::UserInternalError),
             _ => ::std::option::Option::None
         }
     }
@@ -251,6 +253,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceErrorCode {
             WorkspaceErrorCode::AppIdInvalid,
             WorkspaceErrorCode::DatabaseConnectionFail,
             WorkspaceErrorCode::DatabaseInternalError,
+            WorkspaceErrorCode::UserInternalError,
         ];
         values
     }
@@ -281,34 +284,36 @@ 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*\xb6\x01\n\x12WorkspaceErrorCode\x12\x0b\n\x07Unknown\x10\0\
+    \tR\x03msg*\xcd\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\x06J\xcf\x03\n\x06\x12\x04\0\0\x0e\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\x04\
-    \x20\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\x16\n\x0c\n\x05\x04\0\
-    \x02\0\x01\x12\x03\x03\x17\x1b\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\
-    \x1e\x1f\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\x0e\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x17\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\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\
-    \x08\x04\x18\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1b\x1c\n\x0b\n\
-    \x04\x05\0\x02\x02\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\
-    \x03\t\x04\x16\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x19\x1a\n\x0b\n\
-    \x04\x05\0\x02\x03\x12\x03\n\x04\x1d\n\x0c\n\x05\x05\0\x02\x03\x01\x12\
-    \x03\n\x04\x18\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1b\x1c\n\x0b\n\
-    \x04\x05\0\x02\x04\x12\x03\x0b\x04\x15\n\x0c\n\x05\x05\0\x02\x04\x01\x12\
-    \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\x1db\x06p\
-    roto3\
+    DatabaseInternalError\x10\x06\x12\x15\n\x11UserInternalError\x10\nJ\xf8\
+    \x03\n\x06\x12\x04\0\0\x0f\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\x04\x20\n\x0c\n\x05\x04\0\x02\0\x06\
+    \x12\x03\x03\x04\x16\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x17\x1b\n\
+    \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x1e\x1f\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\x0f\x01\n\n\n\
+    \x03\x05\0\x01\x12\x03\x06\x05\x17\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\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x18\n\x0c\n\x05\
+    \x05\0\x02\x01\x02\x12\x03\x08\x1b\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\
+    \t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x16\n\x0c\n\x05\
+    \x05\0\x02\x02\x02\x12\x03\t\x19\x1a\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\
+    \x04\x1d\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x18\n\x0c\n\x05\x05\
+    \0\x02\x03\x02\x12\x03\n\x1b\x1c\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\
+    \x04\x15\n\x0c\n\x05\x05\0\x02\x04\x01\x12\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\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\x1ab\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -9,3 +9,9 @@ message CreateAppRequest {
 message ColorStyle {
     string theme_color = 1;
 }
+message AppDetail {
+    string id = 1;
+    string workspace_id = 2;
+    string name = 3;
+    string desc = 4;
+}

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

@@ -12,4 +12,5 @@ enum WorkspaceErrorCode {
     AppIdInvalid = 4;
     DatabaseConnectionFail = 5;
     DatabaseInternalError = 6;
+    UserInternalError = 10;
 }

+ 34 - 0
rust-lib/flowy-workspace/src/services/app_controller.rs

@@ -0,0 +1,34 @@
+use crate::{
+    entities::app::{AppDetail, CreateAppParams, *},
+    errors::*,
+    module::WorkspaceUser,
+    sql_tables::app::*,
+};
+use flowy_database::{prelude::*, schema::app_table};
+use std::sync::Arc;
+
+pub struct AppController {
+    user: Arc<dyn WorkspaceUser>,
+}
+
+impl AppController {
+    pub fn new(user: Arc<dyn WorkspaceUser>) -> Self { Self { user } }
+
+    pub fn save_app(&self, params: CreateAppParams) -> Result<AppDetail, WorkspaceError> {
+        let app = App::new(params);
+        let conn = self.user.db_connection()?;
+
+        let detail: AppDetail = app.clone().into();
+        let _ = diesel::insert_into(app_table::table)
+            .values(app)
+            .execute(&*conn)?;
+        Ok(detail)
+    }
+
+    pub fn update_app(&self, params: UpdateAppParams) -> Result<(), WorkspaceError> {
+        let changeset = AppChangeset::new(params);
+        let conn = self.user.db_connection()?;
+        diesel_update_table!(app_table, changeset, conn);
+        Ok(())
+    }
+}

+ 1 - 0
rust-lib/flowy-workspace/src/services/helper.rs

@@ -0,0 +1 @@
+

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

@@ -1,4 +1,7 @@
+mod app_controller;
 mod database;
+mod helper;
 mod workspace_controller;
 
+pub use app_controller::*;
 pub use workspace_controller::*;

+ 6 - 58
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -1,46 +1,20 @@
-use crate::{
-    entities::{app::*, workspace::*},
-    errors::*,
-    sql_tables::{app::*, workspace::*},
-};
-use diesel::ExpressionMethods;
-use flowy_database::{
-    query_dsl::*,
-    schema::{app_table, workspace_table},
-    DBConnection,
-    RunQueryDsl,
-    UserDatabaseConnection,
-};
+use crate::{entities::workspace::*, errors::*, module::WorkspaceUser, sql_tables::workspace::*};
+use flowy_database::{prelude::*, schema::workspace_table};
 use std::sync::Arc;
 
-macro_rules! diesel_update_table {
-    (
-        $table_name:ident,
-        $changeset:ident,
-        $connection:ident
-    ) => {
-        let filter =
-            $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($changeset.id.clone()));
-        let affected_row = diesel::update(filter)
-            .set($changeset)
-            .execute(&*$connection)?;
-        debug_assert_eq!(affected_row, 1);
-    };
-}
-
 pub struct WorkspaceController {
-    db: Arc<dyn UserDatabaseConnection>,
+    pub(crate) user: Arc<dyn WorkspaceUser>,
 }
 
 impl WorkspaceController {
-    pub fn new(db: Arc<dyn UserDatabaseConnection>) -> Self { Self { db } }
+    pub fn new(user: Arc<dyn WorkspaceUser>) -> Self { Self { user } }
 
     pub fn save_workspace(
         &self,
         params: CreateWorkspaceParams,
     ) -> Result<WorkspaceDetail, WorkspaceError> {
         let workspace = Workspace::new(params);
-        let conn = self.get_connection()?;
+        let conn = self.user.db_connection()?;
         let detail: WorkspaceDetail = workspace.clone().into();
 
         let _ = diesel::insert_into(workspace_table::table)
@@ -52,35 +26,9 @@ impl WorkspaceController {
 
     pub fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), WorkspaceError> {
         let changeset = WorkspaceChangeset::new(params);
-        let conn = self.get_connection()?;
+        let conn = self.user.db_connection()?;
         diesel_update_table!(workspace_table, changeset, conn);
 
         Ok(())
     }
-
-    pub fn save_app(&self, params: CreateAppParams) -> Result<(), WorkspaceError> {
-        let app = App::new(params);
-        let conn = self.get_connection()?;
-        let _ = diesel::insert_into(app_table::table)
-            .values(app)
-            .execute(&*conn)?;
-        Ok(())
-    }
-
-    pub fn update_app(&self, params: UpdateAppParams) -> Result<(), WorkspaceError> {
-        let changeset = AppChangeset::new(params);
-        let conn = self.get_connection()?;
-        diesel_update_table!(app_table, changeset, conn);
-        Ok(())
-    }
-}
-
-impl WorkspaceController {
-    fn get_connection(&self) -> Result<DBConnection, WorkspaceError> {
-        self.db.get_connection().map_err(|e| {
-            ErrorBuilder::new(WorkspaceErrorCode::DatabaseConnectionFail)
-                .msg(e)
-                .build()
-        })
-    }
 }

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

@@ -1,10 +1,10 @@
 use crate::{
-    entities::app::{ColorStyle, CreateAppParams, UpdateAppParams},
+    entities::app::{AppDetail, ColorStyle, CreateAppParams, UpdateAppParams},
     impl_sql_binary_expression,
     sql_tables::workspace::Workspace,
 };
 use diesel::sql_types::Binary;
-use flowy_database::schema::{app_table, app_table::dsl};
+use flowy_database::schema::app_table;
 use flowy_infra::{timestamp, uuid};
 use serde::{Deserialize, Serialize, __private::TryFrom};
 use std::convert::TryInto;
@@ -104,3 +104,14 @@ impl AppChangeset {
         }
     }
 }
+
+impl std::convert::Into<AppDetail> for App {
+    fn into(self) -> AppDetail {
+        AppDetail {
+            id: self.id,
+            workspace_id: self.workspace_id,
+            name: self.name,
+            desc: self.desc,
+        }
+    }
+}

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

@@ -1,8 +1,5 @@
-use crate::{
-    entities::workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceDetail},
-    sql_tables::app::App,
-};
-use flowy_database::schema::{workspace_table, workspace_table::dsl};
+use crate::entities::workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceDetail};
+use flowy_database::schema::workspace_table;
 use flowy_infra::{timestamp, uuid};
 use serde::{Deserialize, Serialize};
 

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

@@ -0,0 +1,15 @@
+// #[test]
+// fn app_create_success() {
+//     let request = CreateAppRequest {
+//         workspace_id: "".to_string(),
+//         name: "123".to_owned(),
+//         desc: "".to_owned(),
+//         color_style: Default::default(),
+//     };
+//
+//     let response = WorkspaceEventTester::new(CreateWorkspace)
+//         .request(request)
+//         .sync_send()
+//         .parse::<WorkspaceDetail>();
+//     dbg!(&response);
+// }

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

@@ -2,3 +2,10 @@ use flowy_test::EventTester;
 use flowy_workspace::errors::WorkspaceError;
 
 pub type WorkspaceEventTester = EventTester<WorkspaceError>;
+
+pub(crate) fn invalid_workspace_name_test_case() -> Vec<String> {
+    vec!["", "1234".repeat(100).as_str()]
+        .iter()
+        .map(|s| s.to_string())
+        .collect::<Vec<_>>()
+}

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

@@ -1,2 +1,3 @@
+mod app_test;
 mod helper;
 mod workspace_test;

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

@@ -1,4 +1,4 @@
-use crate::helper::WorkspaceEventTester;
+use crate::helper::*;
 use flowy_workspace::{
     entities::workspace::{CreateWorkspaceRequest, WorkspaceDetail},
     event::WorkspaceEvent::*,
@@ -6,10 +6,9 @@ use flowy_workspace::{
 };
 
 #[test]
-#[should_panic]
-fn workspace_create_test() {
+fn workspace_create_success() {
     let request = CreateWorkspaceRequest {
-        name: "".to_owned(),
+        name: "123".to_owned(),
         desc: "".to_owned(),
     };
 
@@ -19,3 +18,41 @@ fn workspace_create_test() {
         .parse::<WorkspaceDetail>();
     dbg!(&response);
 }
+
+#[test]
+fn workspace_create_with_invalid_name_test() {
+    for name in invalid_workspace_name_test_case() {
+        let request = CreateWorkspaceRequest {
+            name,
+            desc: "".to_owned(),
+        };
+
+        assert_eq!(
+            WorkspaceEventTester::new(CreateWorkspace)
+                .request(request)
+                .sync_send()
+                .error()
+                .code,
+            WorkspaceErrorCode::WorkspaceNameInvalid
+        )
+    }
+}
+
+// #[test]
+// fn workspace_update_with_invalid_name_test() {
+//     for name in invalid_workspace_name_test_case() {
+//         let request = CreateWorkspaceRequest {
+//             name,
+//             desc: "".to_owned(),
+//         };
+//
+//         assert_eq!(
+//             WorkspaceEventTester::new(CreateWorkspace)
+//                 .request(request)
+//                 .sync_send()
+//                 .error()
+//                 .code,
+//             WorkspaceErrorCode::WorkspaceNameInvalid
+//         )
+//     }
+// }