appflowy пре 3 година
родитељ
комит
7cf563614b
57 измењених фајлова са 2650 додато и 141 уклоњено
  1. 155 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pb.dart
  2. 20 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_update.pbjson.dart
  3. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart
  4. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart
  5. 88 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart
  6. 15 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart
  7. 47 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_delete.pb.dart
  8. 10 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_delete.pbjson.dart
  9. 61 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pb.dart
  10. 11 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbjson.dart
  11. 101 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_update.pb.dart
  12. 16 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_update.pbjson.dart
  13. 5 1
      backend/Cargo.toml
  14. 4 0
      backend/doc/database_setup.md
  15. 4 4
      backend/src/application.rs
  16. 1 0
      backend/src/entities/mod.rs
  17. 2 2
      backend/src/entities/token.rs
  18. 1 1
      backend/src/entities/user.rs
  19. 38 0
      backend/src/entities/workspace.rs
  20. 1 0
      backend/src/lib.rs
  21. 3 0
      backend/src/sqlx_ext/mod.rs
  22. 38 0
      backend/src/sqlx_ext/query.rs
  23. 5 5
      backend/src/user_service/auth.rs
  24. 0 0
      backend/src/workspace_service/app/app.rs
  25. 1 0
      backend/src/workspace_service/app/mod.rs
  26. 3 0
      backend/src/workspace_service/workspace/mod.rs
  27. 31 11
      backend/src/workspace_service/workspace/router.rs
  28. 167 0
      backend/src/workspace_service/workspace/workspace.rs
  29. 26 0
      backend/tests/api/helper.rs
  30. 1 0
      backend/tests/api/main.rs
  31. 35 0
      backend/tests/api/workspace.rs
  32. 5 0
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  33. 1 0
      rust-lib/flowy-net/Cargo.toml
  34. 1 0
      rust-lib/flowy-net/src/errors.rs
  35. 10 2
      rust-lib/flowy-net/src/response/response.rs
  36. 10 0
      rust-lib/flowy-user/src/entities/user_update.rs
  37. 530 21
      rust-lib/flowy-user/src/protobuf/model/user_update.rs
  38. 7 0
      rust-lib/flowy-user/src/protobuf/proto/user_update.proto
  39. 1 0
      rust-lib/flowy-workspace/Cargo.toml
  40. 1 1
      rust-lib/flowy-workspace/src/entities/workspace/mod.rs
  41. 19 0
      rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs
  42. 2 0
      rust-lib/flowy-workspace/src/entities/workspace/workspace_delete.rs
  43. 4 0
      rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs
  44. 6 0
      rust-lib/flowy-workspace/src/entities/workspace/workspace_update.rs
  45. 11 0
      rust-lib/flowy-workspace/src/errors.rs
  46. 12 6
      rust-lib/flowy-workspace/src/lib.rs
  47. 44 39
      rust-lib/flowy-workspace/src/protobuf/model/errors.rs
  48. 326 30
      rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs
  49. 170 6
      rust-lib/flowy-workspace/src/protobuf/model/workspace_delete.rs
  50. 211 8
      rust-lib/flowy-workspace/src/protobuf/model/workspace_query.rs
  51. 339 2
      rust-lib/flowy-workspace/src/protobuf/model/workspace_update.rs
  52. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/errors.proto
  53. 5 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_create.proto
  54. 3 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_delete.proto
  55. 4 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_query.proto
  56. 5 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_update.proto
  57. 28 1
      rust-lib/flowy-workspace/src/services/workspace_controller.rs

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

@@ -164,3 +164,158 @@ class UpdateUserRequest extends $pb.GeneratedMessage {
   void clearPassword() => clearField(5);
 }
 
+enum UpdateUserParams_OneOfName {
+  name, 
+  notSet
+}
+
+enum UpdateUserParams_OneOfEmail {
+  email, 
+  notSet
+}
+
+enum UpdateUserParams_OneOfWorkspace {
+  workspace, 
+  notSet
+}
+
+enum UpdateUserParams_OneOfPassword {
+  password, 
+  notSet
+}
+
+class UpdateUserParams extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, UpdateUserParams_OneOfName> _UpdateUserParams_OneOfNameByTag = {
+    2 : UpdateUserParams_OneOfName.name,
+    0 : UpdateUserParams_OneOfName.notSet
+  };
+  static const $core.Map<$core.int, UpdateUserParams_OneOfEmail> _UpdateUserParams_OneOfEmailByTag = {
+    3 : UpdateUserParams_OneOfEmail.email,
+    0 : UpdateUserParams_OneOfEmail.notSet
+  };
+  static const $core.Map<$core.int, UpdateUserParams_OneOfWorkspace> _UpdateUserParams_OneOfWorkspaceByTag = {
+    4 : UpdateUserParams_OneOfWorkspace.workspace,
+    0 : UpdateUserParams_OneOfWorkspace.notSet
+  };
+  static const $core.Map<$core.int, UpdateUserParams_OneOfPassword> _UpdateUserParams_OneOfPasswordByTag = {
+    5 : UpdateUserParams_OneOfPassword.password,
+    0 : UpdateUserParams_OneOfPassword.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UpdateUserParams', 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
+  ;
+
+  UpdateUserParams._() : super();
+  factory UpdateUserParams({
+    $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 UpdateUserParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory UpdateUserParams.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')
+  UpdateUserParams clone() => UpdateUserParams()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  UpdateUserParams copyWith(void Function(UpdateUserParams) updates) => super.copyWith((message) => updates(message as UpdateUserParams)) as UpdateUserParams; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static UpdateUserParams create() => UpdateUserParams._();
+  UpdateUserParams createEmptyInstance() => create();
+  static $pb.PbList<UpdateUserParams> createRepeated() => $pb.PbList<UpdateUserParams>();
+  @$core.pragma('dart2js:noInline')
+  static UpdateUserParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UpdateUserParams>(create);
+  static UpdateUserParams? _defaultInstance;
+
+  UpdateUserParams_OneOfName whichOneOfName() => _UpdateUserParams_OneOfNameByTag[$_whichOneof(0)]!;
+  void clearOneOfName() => clearField($_whichOneof(0));
+
+  UpdateUserParams_OneOfEmail whichOneOfEmail() => _UpdateUserParams_OneOfEmailByTag[$_whichOneof(1)]!;
+  void clearOneOfEmail() => clearField($_whichOneof(1));
+
+  UpdateUserParams_OneOfWorkspace whichOneOfWorkspace() => _UpdateUserParams_OneOfWorkspaceByTag[$_whichOneof(2)]!;
+  void clearOneOfWorkspace() => clearField($_whichOneof(2));
+
+  UpdateUserParams_OneOfPassword whichOneOfPassword() => _UpdateUserParams_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);
+}
+

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

@@ -28,3 +28,23 @@ const UpdateUserRequest$json = const {
 
 /// Descriptor for `UpdateUserRequest`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List updateUserRequestDescriptor = $convert.base64Decode('ChFVcGRhdGVVc2VyUmVxdWVzdBIOCgJpZBgBIAEoCVICaWQSFAoEbmFtZRgCIAEoCUgAUgRuYW1lEhYKBWVtYWlsGAMgASgJSAFSBWVtYWlsEh4KCXdvcmtzcGFjZRgEIAEoCUgCUgl3b3Jrc3BhY2USHAoIcGFzc3dvcmQYBSABKAlIA1IIcGFzc3dvcmRCDQoLb25lX29mX25hbWVCDgoMb25lX29mX2VtYWlsQhIKEG9uZV9vZl93b3Jrc3BhY2VCEQoPb25lX29mX3Bhc3N3b3Jk');
+@$core.Deprecated('Use updateUserParamsDescriptor instead')
+const UpdateUserParams$json = const {
+  '1': 'UpdateUserParams',
+  '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 `UpdateUserParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List updateUserParamsDescriptor = $convert.base64Decode('ChBVcGRhdGVVc2VyUGFyYW1zEg4KAmlkGAEgASgJUgJpZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFgoFZW1haWwYAyABKAlIAVIFZW1haWwSHgoJd29ya3NwYWNlGAQgASgJSAJSCXdvcmtzcGFjZRIcCghwYXNzd29yZBgFIAEoCUgDUghwYXNzd29yZEINCgtvbmVfb2ZfbmFtZUIOCgxvbmVfb2ZfZW1haWxCEgoQb25lX29mX3dvcmtzcGFjZUIRCg9vbmVfb2ZfcGFzc3dvcmQ=');

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

@@ -24,6 +24,7 @@ class WsErrCode extends $pb.ProtobufEnum {
   static const WsErrCode WorkspaceDatabaseError = WsErrCode._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDatabaseError');
   static const WsErrCode UserInternalError = WsErrCode._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserInternalError');
   static const WsErrCode UserNotLoginYet = WsErrCode._(103, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
+  static const WsErrCode ServerError = WsErrCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ServerError');
 
   static const $core.List<WsErrCode> values = <WsErrCode> [
     Unknown,
@@ -40,6 +41,7 @@ class WsErrCode extends $pb.ProtobufEnum {
     WorkspaceDatabaseError,
     UserInternalError,
     UserNotLoginYet,
+    ServerError,
   ];
 
   static final $core.Map<$core.int, WsErrCode> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -26,11 +26,12 @@ const WsErrCode$json = const {
     const {'1': 'WorkspaceDatabaseError', '2': 101},
     const {'1': 'UserInternalError', '2': 102},
     const {'1': 'UserNotLoginYet', '2': 103},
+    const {'1': 'ServerError', '2': 1000},
   ],
 };
 
 /// Descriptor for `WsErrCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List wsErrCodeDescriptor = $convert.base64Decode('CglXc0VyckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQChISCg5BcHBOYW1lSW52YWxpZBALEhMKD1ZpZXdOYW1lSW52YWxpZBAUEhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEBUSEQoNVmlld0lkSW52YWxpZBAWEhMKD1ZpZXdEZXNjSW52YWxpZBAXEhoKFkRhdGFiYXNlQ29ubmVjdGlvbkZhaWwQZBIaChZXb3Jrc3BhY2VEYXRhYmFzZUVycm9yEGUSFQoRVXNlckludGVybmFsRXJyb3IQZhITCg9Vc2VyTm90TG9naW5ZZXQQZw==');
+final $typed_data.Uint8List wsErrCodeDescriptor = $convert.base64Decode('CglXc0VyckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQChISCg5BcHBOYW1lSW52YWxpZBALEhMKD1ZpZXdOYW1lSW52YWxpZBAUEhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEBUSEQoNVmlld0lkSW52YWxpZBAWEhMKD1ZpZXdEZXNjSW52YWxpZBAXEhoKFkRhdGFiYXNlQ29ubmVjdGlvbkZhaWwQZBIaChZXb3Jrc3BhY2VEYXRhYmFzZUVycm9yEGUSFQoRVXNlckludGVybmFsRXJyb3IQZhITCg9Vc2VyTm90TG9naW5ZZXQQZxIQCgtTZXJ2ZXJFcnJvchDoBw==');
 @$core.Deprecated('Use workspaceErrorDescriptor instead')
 const WorkspaceError$json = const {
   '1': 'WorkspaceError',

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

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

+ 15 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart

@@ -19,6 +19,21 @@ const CreateWorkspaceRequest$json = const {
 
 /// Descriptor for `CreateWorkspaceRequest`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List createWorkspaceRequestDescriptor = $convert.base64Decode('ChZDcmVhdGVXb3Jrc3BhY2VSZXF1ZXN0EhIKBG5hbWUYASABKAlSBG5hbWUSEgoEZGVzYxgCIAEoCVIEZGVzYw==');
+@$core.Deprecated('Use createWorkspaceParamsDescriptor instead')
+const CreateWorkspaceParams$json = const {
+  '1': 'CreateWorkspaceParams',
+  '2': const [
+    const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
+    const {'1': 'desc', '3': 2, '4': 1, '5': 9, '10': 'desc'},
+    const {'1': 'user_id', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'userId'},
+  ],
+  '8': const [
+    const {'1': 'one_of_user_id'},
+  ],
+};
+
+/// Descriptor for `CreateWorkspaceParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List createWorkspaceParamsDescriptor = $convert.base64Decode('ChVDcmVhdGVXb3Jrc3BhY2VQYXJhbXMSEgoEbmFtZRgBIAEoCVIEbmFtZRISCgRkZXNjGAIgASgJUgRkZXNjEhkKB3VzZXJfaWQYAyABKAlIAFIGdXNlcklkQhAKDm9uZV9vZl91c2VyX2lk');
 @$core.Deprecated('Use workspaceDescriptor instead')
 const Workspace$json = const {
   '1': 'Workspace',

+ 47 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_delete.pb.dart

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

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

@@ -18,3 +18,13 @@ const DeleteWorkspaceRequest$json = const {
 
 /// Descriptor for `DeleteWorkspaceRequest`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List deleteWorkspaceRequestDescriptor = $convert.base64Decode('ChZEZWxldGVXb3Jrc3BhY2VSZXF1ZXN0EiEKDHdvcmtzcGFjZV9pZBgBIAEoCVILd29ya3NwYWNlSWQ=');
+@$core.Deprecated('Use deleteWorkspaceParamsDescriptor instead')
+const DeleteWorkspaceParams$json = const {
+  '1': 'DeleteWorkspaceParams',
+  '2': const [
+    const {'1': 'workspace_id', '3': 1, '4': 1, '5': 9, '10': 'workspaceId'},
+  ],
+};
+
+/// Descriptor for `DeleteWorkspaceParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List deleteWorkspaceParamsDescriptor = $convert.base64Decode('ChVEZWxldGVXb3Jrc3BhY2VQYXJhbXMSIQoMd29ya3NwYWNlX2lkGAEgASgJUgt3b3Jrc3BhY2VJZA==');

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

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

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

@@ -19,3 +19,14 @@ const QueryWorkspaceRequest$json = const {
 
 /// Descriptor for `QueryWorkspaceRequest`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List queryWorkspaceRequestDescriptor = $convert.base64Decode('ChVRdWVyeVdvcmtzcGFjZVJlcXVlc3QSIQoMd29ya3NwYWNlX2lkGAEgASgJUgt3b3Jrc3BhY2VJZBIbCglyZWFkX2FwcHMYAiABKAhSCHJlYWRBcHBz');
+@$core.Deprecated('Use queryWorkspaceParamsDescriptor instead')
+const QueryWorkspaceParams$json = const {
+  '1': 'QueryWorkspaceParams',
+  '2': const [
+    const {'1': 'workspace_id', '3': 1, '4': 1, '5': 9, '10': 'workspaceId'},
+    const {'1': 'read_apps', '3': 2, '4': 1, '5': 8, '10': 'readApps'},
+  ],
+};
+
+/// Descriptor for `QueryWorkspaceParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List queryWorkspaceParamsDescriptor = $convert.base64Decode('ChRRdWVyeVdvcmtzcGFjZVBhcmFtcxIhCgx3b3Jrc3BhY2VfaWQYASABKAlSC3dvcmtzcGFjZUlkEhsKCXJlYWRfYXBwcxgCIAEoCFIIcmVhZEFwcHM=');

+ 101 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_update.pb.dart

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

+ 16 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_update.pbjson.dart

@@ -24,3 +24,19 @@ const UpdateWorkspaceRequest$json = const {
 
 /// Descriptor for `UpdateWorkspaceRequest`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List updateWorkspaceRequestDescriptor = $convert.base64Decode('ChZVcGRhdGVXb3Jrc3BhY2VSZXF1ZXN0Eg4KAmlkGAEgASgJUgJpZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFAoEZGVzYxgDIAEoCUgBUgRkZXNjQg0KC29uZV9vZl9uYW1lQg0KC29uZV9vZl9kZXNj');
+@$core.Deprecated('Use updateWorkspaceParamsDescriptor instead')
+const UpdateWorkspaceParams$json = const {
+  '1': 'UpdateWorkspaceParams',
+  '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': 'desc', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'desc'},
+  ],
+  '8': const [
+    const {'1': 'one_of_name'},
+    const {'1': 'one_of_desc'},
+  ],
+};
+
+/// Descriptor for `UpdateWorkspaceParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List updateWorkspaceParamsDescriptor = $convert.base64Decode('ChVVcGRhdGVXb3Jrc3BhY2VQYXJhbXMSDgoCaWQYASABKAlSAmlkEhQKBG5hbWUYAiABKAlIAFIEbmFtZRIUCgRkZXNjGAMgASgJSAFSBGRlc2NCDQoLb25lX29mX25hbWVCDQoLb25lX29mX2Rlc2M=');

+ 5 - 1
backend/Cargo.toml

@@ -37,8 +37,10 @@ jsonwebtoken = "7.2"
 
 flowy-log = { path = "../rust-lib/flowy-log" }
 flowy-user = { path = "../rust-lib/flowy-user" }
+flowy-workspace = { path = "../rust-lib/flowy-workspace" }
 flowy-net = { path = "../rust-lib/flowy-net", features = ["http"] }
 
+ormx = { version = "0.7", features = ["postgres"]}
 [dependencies.sqlx]
 version = "0.5.6"
 default-features = false
@@ -50,6 +52,7 @@ features = [
     "chrono",
     "migrate",
     "offline",
+    "any",
 ]
 
 
@@ -65,4 +68,5 @@ once_cell = "1.7.2"
 actix-rt = "2"
 tokio = { version = "1", features = ["macros"] }
 linkify = "0.5.0"
-flowy-user = { path = "../rust-lib/flowy-user" }
+flowy-user = { path = "../rust-lib/flowy-user" }
+flowy-workspace = { path = "../rust-lib/flowy-workspace" }

+ 4 - 0
backend/doc/database_setup.md

@@ -49,6 +49,10 @@ By default, Docker images do not expose their ports to the underlying host machi
     * sqlx: sqlx database reset
     * diesel: diesel database reset
 
+**offline mode**
+
+`cargo sqlx prepare -- --bin backend`
+
 **Type mapping**
 * [postgres type map](https://docs.rs/sqlx/0.5.7/sqlx/postgres/types/index.html)
 * [postgres and diesel type map](https://kotiri.com/2018/01/31/postgresql-diesel-rust-types.html)

+ 4 - 4
backend/src/application.rs

@@ -74,10 +74,10 @@ fn user_scope() -> Scope {
             .route(web::get().to(user::user_profile))
         )
         .service(web::resource("/workspace")
-            .route(web::post().to(workspace::create_workspace))
-            .route(web::delete().to(workspace::delete_workspace))
-            .route(web::get().to(workspace::read_workspace))
-            .route(web::patch().to(workspace::update_workspace))
+            .route(web::post().to(workspace::create_handler))
+            .route(web::delete().to(workspace::delete_handler))
+            .route(web::get().to(workspace::read_handler))
+            .route(web::patch().to(workspace::update_handler))
         )
         .service(web::resource("/app")
             .route(web::post().to(app::create_app))

+ 1 - 0
backend/src/entities/mod.rs

@@ -1,2 +1,3 @@
 pub mod token;
 pub mod user;
+pub mod workspace;

+ 2 - 2
backend/src/entities/token.rs

@@ -1,6 +1,6 @@
 use crate::{
     config::env::{domain, jwt_secret},
-    entities::user::User,
+    entities::user::UserTable,
 };
 use chrono::{Duration, Local};
 use derive_more::{From, Into};
@@ -43,7 +43,7 @@ impl Claim {
 #[derive(From, Into)]
 pub struct Token(String);
 impl Token {
-    pub fn create_token(data: &User) -> Result<Self, ServerError> {
+    pub fn create_token(data: &UserTable) -> Result<Self, ServerError> {
         let claims = Claim::with_email(&data.email);
         encode(
             &Header::new(DEFAULT_ALGORITHM),

+ 1 - 1
backend/src/entities/user.rs

@@ -3,7 +3,7 @@
 use chrono::Utc;
 
 #[derive(Debug, Clone, sqlx::FromRow)]
-pub struct User {
+pub struct UserTable {
     pub(crate) id: uuid::Uuid,
     pub(crate) email: String,
     pub(crate) name: String,

+ 38 - 0
backend/src/entities/workspace.rs

@@ -0,0 +1,38 @@
+use chrono::Utc;
+
+#[derive(Debug, Clone, sqlx::FromRow)]
+pub struct WorkspaceTable {
+    pub(crate) id: uuid::Uuid,
+    pub(crate) name: String,
+    pub(crate) description: String,
+    pub(crate) modified_time: chrono::DateTime<Utc>,
+    pub(crate) create_time: chrono::DateTime<Utc>,
+    pub(crate) user_id: String,
+}
+
+#[derive(Debug, Clone, sqlx::FromRow)]
+pub struct AppTable {
+    pub(crate) id: uuid::Uuid,
+    pub(crate) workspace_id: String,
+    pub(crate) name: String,
+    pub(crate) description: String,
+    pub(crate) color_style: Vec<u8>,
+    pub(crate) last_view_id: String,
+    pub(crate) modified_time: chrono::DateTime<Utc>,
+    pub(crate) create_time: chrono::DateTime<Utc>,
+    pub(crate) user_id: String,
+    pub(crate) is_trash: bool,
+}
+
+#[derive(Debug, Clone, sqlx::FromRow)]
+pub struct ViewTable {
+    pub(crate) id: uuid::Uuid,
+    pub(crate) belong_to_id: String,
+    pub(crate) name: String,
+    pub(crate) description: String,
+    pub(crate) modified_time: chrono::DateTime<Utc>,
+    pub(crate) create_time: chrono::DateTime<Utc>,
+    pub(crate) thumbnail: String,
+    pub(crate) view_type: i32,
+    pub(crate) is_trash: bool,
+}

+ 1 - 0
backend/src/lib.rs

@@ -3,6 +3,7 @@ pub mod config;
 mod context;
 mod entities;
 mod routers;
+mod sqlx_ext;
 pub mod user_service;
 pub mod workspace_service;
 pub mod ws_service;

+ 3 - 0
backend/src/sqlx_ext/mod.rs

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

+ 38 - 0
backend/src/sqlx_ext/query.rs

@@ -0,0 +1,38 @@
+use sqlx::{any::AnyArguments, Arguments, Encode, PgPool, Postgres, Type};
+
+use sqlx::postgres::PgArguments;
+
+pub struct UpdateBuilder {
+    arguments: PgArguments,
+    table: String,
+    fields: String,
+}
+
+impl UpdateBuilder {
+    pub fn new(table: &str) -> Self {
+        Self {
+            table: table.to_owned(),
+            fields: String::new(),
+            arguments: PgArguments::default(),
+        }
+    }
+
+    pub fn add_argument<'a, T>(&mut self, column: &str, arg: Option<T>)
+    where
+        T: 'a + Send + Encode<'a, Postgres> + Type<Postgres>,
+    {
+        if let Some(arg) = arg {
+            if self.fields.is_empty() {
+                self.fields += &format!("{}=?", column);
+            } else {
+                self.fields += &format!(", {}=?", column);
+            }
+            self.arguments.add(arg);
+        }
+    }
+
+    pub fn build(self) -> (String, PgArguments) {
+        let sql = format!("UPDATE {} SET {} WHERE id=?", self.table, self.fields);
+        (sql, self.arguments)
+    }
+}

+ 5 - 5
backend/src/user_service/auth.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::{token::Token, user::User},
+    entities::{token::Token, user::UserTable},
     user_service::{hash_password, verify_password},
 };
 use actix_identity::Identity;
@@ -48,7 +48,7 @@ pub async fn sign_in(
                 token: token.into(),
             };
             id.remember(data.token.clone());
-            FlowyResponse::success(data)
+            FlowyResponse::success().data(data)
         },
         _ => Err(ServerError::password_not_match()),
     }
@@ -85,7 +85,7 @@ pub async fn register_user(
         .await
         .context("Failed to commit SQL transaction to register user.")?;
 
-    FlowyResponse::success(data)
+    FlowyResponse::success().data(data)
 }
 
 async fn is_email_exist(
@@ -111,8 +111,8 @@ async fn is_email_exist(
 async fn read_user(
     transaction: &mut Transaction<'_, Postgres>,
     email: &str,
-) -> Result<User, ServerError> {
-    let user = sqlx::query_as::<Postgres, User>("SELECT * FROM user_table WHERE email = $1")
+) -> Result<UserTable, ServerError> {
+    let user = sqlx::query_as::<Postgres, UserTable>("SELECT * FROM user_table WHERE email = $1")
         .bind(email)
         .fetch_one(transaction)
         .await

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


+ 1 - 0
backend/src/workspace_service/app/mod.rs

@@ -1 +1,2 @@
+pub mod app;
 pub mod router;

+ 3 - 0
backend/src/workspace_service/workspace/mod.rs

@@ -1 +1,4 @@
 pub mod router;
+mod workspace;
+
+pub use workspace::*;

+ 31 - 11
backend/src/workspace_service/workspace/router.rs

@@ -1,3 +1,12 @@
+use crate::{
+    routers::utils::parse_from_payload,
+    workspace_service::workspace::{
+        create_workspace,
+        delete_workspace,
+        read_workspace,
+        update_workspace,
+    },
+};
 use actix_identity::Identity;
 use actix_web::{
     web::{Data, Payload},
@@ -6,36 +15,47 @@ use actix_web::{
     HttpResponse,
 };
 use flowy_net::errors::ServerError;
+use flowy_workspace::protobuf::{
+    CreateWorkspaceParams,
+    DeleteWorkspaceParams,
+    QueryWorkspaceParams,
+    UpdateWorkspaceParams,
+};
 use sqlx::PgPool;
 
-pub async fn create_workspace(
+pub async fn create_handler(
     payload: Payload,
-    id: Identity,
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
-    unimplemented!()
+    let params: CreateWorkspaceParams = parse_from_payload(payload).await?;
+    let resp = create_workspace(pool.get_ref(), params).await?;
+    Ok(resp.into())
 }
 
-pub async fn read_workspace(
+pub async fn read_handler(
     payload: Payload,
-    id: Identity,
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
-    unimplemented!()
+    let params: QueryWorkspaceParams = parse_from_payload(payload).await?;
+    let resp = read_workspace(pool.get_ref(), params).await?;
+    Ok(resp.into())
 }
 
-pub async fn delete_workspace(
+pub async fn delete_handler(
     payload: Payload,
-    id: Identity,
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
-    unimplemented!()
+    let params: DeleteWorkspaceParams = parse_from_payload(payload).await?;
+    let resp = delete_workspace(pool.get_ref(), params).await?;
+    Ok(resp.into())
 }
 
-pub async fn update_workspace(
+pub async fn update_handler(
     payload: Payload,
     id: Identity,
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
-    unimplemented!()
+    let params: UpdateWorkspaceParams = parse_from_payload(payload).await?;
+    let resp = update_workspace(pool.get_ref(), params).await?;
+    Ok(resp.into())
 }

+ 167 - 0
backend/src/workspace_service/workspace/workspace.rs

@@ -0,0 +1,167 @@
+use crate::{entities::workspace::WorkspaceTable, sqlx_ext::UpdateBuilder};
+use anyhow::Context;
+use chrono::Utc;
+use flowy_net::{errors::ServerError, response::FlowyResponse};
+use flowy_user::entities::parser::UserId;
+use flowy_workspace::{
+    entities::{
+        app::RepeatedApp,
+        workspace::{
+            parser::{WorkspaceId, WorkspaceName},
+            Workspace,
+        },
+    },
+    protobuf::{
+        CreateWorkspaceParams,
+        DeleteWorkspaceParams,
+        QueryWorkspaceParams,
+        UpdateWorkspaceParams,
+    },
+};
+use sqlx::{PgPool, Postgres};
+use uuid::Uuid;
+
+pub(crate) async fn create_workspace(
+    pool: &PgPool,
+    params: CreateWorkspaceParams,
+) -> Result<FlowyResponse, ServerError> {
+    let name = WorkspaceName::parse(params.get_name().to_owned())
+        .map_err(|e| ServerError::params_invalid().context(e))?;
+    let user_id = UserId::parse(params.get_user_id().to_owned())
+        .map_err(|e| ServerError::params_invalid().context(e))?;
+
+    let mut transaction = pool
+        .begin()
+        .await
+        .context("Failed to acquire a Postgres connection to create workspace")?;
+
+    let uuid = uuid::Uuid::new_v4();
+    let time = Utc::now();
+    let _ = sqlx::query!(
+        r#"
+            INSERT INTO workspace_table (id, name, description, modified_time, create_time, user_id)
+            VALUES ($1, $2, $3, $4, $5, $6)
+        "#,
+        uuid,
+        name.as_ref(),
+        params.desc,
+        time,
+        time,
+        user_id.as_ref(),
+    )
+    .execute(&mut transaction)
+    .await
+    .map_err(|e| ServerError::internal().context(e))?;
+
+    transaction
+        .commit()
+        .await
+        .context("Failed to commit SQL transaction to create workspace.")?;
+
+    let workspace = Workspace {
+        id: uuid.to_string(),
+        name: name.as_ref().to_owned(),
+        desc: params.desc,
+        apps: RepeatedApp::default(),
+    };
+
+    FlowyResponse::success().data(workspace)
+}
+
+pub(crate) async fn read_workspace(
+    pool: &PgPool,
+    params: QueryWorkspaceParams,
+) -> Result<FlowyResponse, ServerError> {
+    let workspace_id = check_workspace_id(params.get_workspace_id().to_owned())?;
+    let mut transaction = pool
+        .begin()
+        .await
+        .context("Failed to acquire a Postgres connection to read workspace")?;
+
+    let uuid = Uuid::parse_str(workspace_id.as_ref())?;
+    let table =
+        sqlx::query_as::<Postgres, WorkspaceTable>("SELECT * FROM workspace_table WHERE id = $1")
+            .bind(uuid)
+            .fetch_one(&mut transaction)
+            .await
+            .map_err(|err| {
+                //
+                ServerError::internal().context(err)
+            })?;
+
+    transaction
+        .commit()
+        .await
+        .context("Failed to commit SQL transaction to read workspace.")?;
+
+    let mut workspace = Workspace::new(table.id.to_string(), table.name, table.description);
+
+    if params.get_read_apps() {
+        workspace.apps = RepeatedApp { items: vec![] }
+    }
+
+    FlowyResponse::success().data(workspace)
+}
+
+pub(crate) async fn update_workspace(
+    pool: &PgPool,
+    params: UpdateWorkspaceParams,
+) -> Result<FlowyResponse, ServerError> {
+    let workspace_id = check_workspace_id(params.get_id().to_owned())?;
+    let mut transaction = pool
+        .begin()
+        .await
+        .context("Failed to acquire a Postgres connection to update workspace")?;
+
+    let mut builder = UpdateBuilder::new("workspace_table");
+    if params.has_name() {
+        builder.add_argument("name", Some(params.get_name()));
+    }
+    if params.has_desc() {
+        builder.add_argument("description", Some(params.get_desc()));
+    }
+    builder.add_argument("id", Some(workspace_id.as_ref()));
+    let (sql, args) = builder.build();
+
+    sqlx::query_with(&sql, args)
+        .execute(&mut transaction)
+        .await
+        .map_err(|err| ServerError::internal().context(err))?;
+
+    transaction
+        .commit()
+        .await
+        .context("Failed to commit SQL transaction to update workspace.")?;
+
+    unimplemented!()
+}
+
+pub(crate) async fn delete_workspace(
+    pool: &PgPool,
+    params: DeleteWorkspaceParams,
+) -> Result<FlowyResponse, ServerError> {
+    let workspace_id = check_workspace_id(params.get_workspace_id().to_owned())?;
+    let mut transaction = pool
+        .begin()
+        .await
+        .context("Failed to acquire a Postgres connection to delete workspace")?;
+
+    let _ = sqlx::query(r#"DELETE FROM workspace_table where workspace_id = $1"#)
+        .bind(workspace_id.as_ref())
+        .execute(&mut transaction)
+        .await
+        .map_err(|e| ServerError::internal().context(e))?;
+
+    transaction
+        .commit()
+        .await
+        .context("Failed to commit SQL transaction to delete workspace.")?;
+
+    Ok(FlowyResponse::success())
+}
+
+fn check_workspace_id(id: String) -> Result<WorkspaceId, ServerError> {
+    let workspace_id =
+        WorkspaceId::parse(id).map_err(|e| ServerError::params_invalid().context(e))?;
+    Ok(workspace_id)
+}

+ 26 - 0
backend/tests/api/helper.rs

@@ -4,6 +4,7 @@ use backend::{
 };
 use flowy_net::request::HttpRequestBuilder;
 use flowy_user::prelude::*;
+use flowy_workspace::prelude::*;
 use sqlx::{Connection, Executor, PgConnection, PgPool};
 use uuid::Uuid;
 
@@ -25,6 +26,31 @@ impl TestApp {
         let resp = user_sign_in(params, &url).await.unwrap();
         resp
     }
+
+    pub async fn create_workspace(&self, mut params: CreateWorkspaceParams) -> Workspace {
+        let url = format!("{}/api/workspace", self.address);
+        let response = self.register_test_user().await;
+        params.user_id = Some(response.uid);
+        let workspace = create_workspace_request(params, &url).await.unwrap();
+        workspace
+    }
+
+    pub async fn read_workspace(&self, params: QueryWorkspaceParams) -> Workspace {
+        let url = format!("{}/api/workspace", self.address);
+        let workspace = read_workspace_request(params, &url).await.unwrap();
+        workspace
+    }
+
+    async fn register_test_user(&self) -> SignUpResponse {
+        let params = SignUpParams {
+            email: "[email protected]".to_string(),
+            name: "annie".to_string(),
+            password: "HelloAppFlowy123!".to_string(),
+        };
+
+        let response = self.register_user(params).await;
+        response
+    }
 }
 
 pub async fn spawn_app() -> TestApp {

+ 1 - 0
backend/tests/api/main.rs

@@ -1,2 +1,3 @@
 mod auth;
 mod helper;
+mod workspace;

+ 35 - 0
backend/tests/api/workspace.rs

@@ -0,0 +1,35 @@
+use crate::helper::{spawn_app, TestApp};
+use flowy_workspace::entities::workspace::{CreateWorkspaceParams, QueryWorkspaceParams};
+
+#[actix_rt::test]
+async fn workspace_create() {
+    let app = spawn_app().await;
+
+    let params = CreateWorkspaceParams {
+        name: "My first workspace".to_string(),
+        desc: "This is my first workspace".to_string(),
+        user_id: None,
+    };
+
+    let workspace = app.create_workspace(params).await;
+    log::info!("{:?}", workspace);
+}
+
+#[actix_rt::test]
+async fn workspace_read() {
+    let app = spawn_app().await;
+    let params = CreateWorkspaceParams {
+        name: "My first workspace".to_string(),
+        desc: "This is my first workspace".to_string(),
+        user_id: None,
+    };
+    let workspace_1 = app.create_workspace(params).await;
+
+    let read_params = QueryWorkspaceParams {
+        workspace_id: workspace_1.id.clone(),
+        read_apps: false,
+    };
+    let workspace_2 = app.read_workspace(read_params).await;
+
+    log::info!("{:?}", workspace_2);
+}

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

@@ -25,11 +25,15 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "UpdateAppRequest"
         | "DeleteAppRequest"
         | "UpdateWorkspaceRequest"
+        | "UpdateWorkspaceParams"
         | "DeleteWorkspaceRequest"
+        | "DeleteWorkspaceParams"
         | "CreateWorkspaceRequest"
+        | "CreateWorkspaceParams"
         | "Workspace"
         | "Workspaces"
         | "QueryWorkspaceRequest"
+        | "QueryWorkspaceParams"
         | "CurrentWorkspace"
         | "UpdateViewRequest"
         | "DeleteViewRequest"
@@ -49,6 +53,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "FFIResponse"
         | "UserDetail"
         | "UpdateUserRequest"
+        | "UpdateUserParams"
         | "SignUpRequest"
         | "SignUpParams"
         | "SignUpResponse"

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

@@ -23,6 +23,7 @@ derive_more = {version = "0.99", features = ["display"]}
 flowy-derive = { path = "../flowy-derive" }
 anyhow = "1.0"
 thiserror = "1.0.24"
+uuid = { version = "0.8", features = ["v4"] }
 
 [features]
 http = ["actix-web"]

+ 1 - 0
rust-lib/flowy-net/src/errors.rs

@@ -4,6 +4,7 @@ use serde_repr::*;
 use std::{fmt, fmt::Debug};
 
 use crate::response::FlowyResponse;
+use uuid::Error;
 
 #[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
 pub struct ServerError {

+ 10 - 2
rust-lib/flowy-net/src/response/response.rs

@@ -14,11 +14,15 @@ pub struct FlowyResponse {
 impl FlowyResponse {
     pub fn new(data: Bytes, error: Option<ServerError>) -> Self { FlowyResponse { data, error } }
 
-    pub fn success<T: TryInto<Bytes, Error = protobuf::ProtobufError>>(
+    pub fn success() -> Self { Self::new(Bytes::new(), None) }
+
+    pub fn data<T: TryInto<Bytes, Error = protobuf::ProtobufError>>(
+        mut self,
         data: T,
     ) -> Result<Self, ServerError> {
         let bytes: Bytes = data.try_into()?;
-        Ok(Self::new(bytes, None))
+        self.data = bytes;
+        Ok(self)
     }
 }
 
@@ -77,3 +81,7 @@ impl std::convert::From<reqwest::Error> for ServerError {
         ServerError::internal().context(error)
     }
 }
+
+impl std::convert::From<uuid::Error> for ServerError {
+    fn from(e: uuid::Error) -> Self { ServerError::internal().context(e) }
+}

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

@@ -52,11 +52,21 @@ impl UpdateUserRequest {
     }
 }
 
+#[derive(ProtoBuf, Default)]
 pub struct UpdateUserParams {
+    #[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>,
 }
 

+ 530 - 21
rust-lib/flowy-user/src/protobuf/model/user_update.rs

@@ -507,33 +507,542 @@ impl ::protobuf::reflect::ProtobufValue for UpdateUserRequest {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct UpdateUserParams {
+    // message fields
+    pub id: ::std::string::String,
+    // message oneof groups
+    pub one_of_name: ::std::option::Option<UpdateUserParams_oneof_one_of_name>,
+    pub one_of_email: ::std::option::Option<UpdateUserParams_oneof_one_of_email>,
+    pub one_of_workspace: ::std::option::Option<UpdateUserParams_oneof_one_of_workspace>,
+    pub one_of_password: ::std::option::Option<UpdateUserParams_oneof_one_of_password>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UpdateUserParams {
+    fn default() -> &'a UpdateUserParams {
+        <UpdateUserParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserParams_oneof_one_of_name {
+    name(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserParams_oneof_one_of_email {
+    email(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserParams_oneof_one_of_workspace {
+    workspace(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateUserParams_oneof_one_of_password {
+    password(::std::string::String),
+}
+
+impl UpdateUserParams {
+    pub fn new() -> UpdateUserParams {
+        ::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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_oneof_one_of_name::name(_)) = self.one_of_name {
+        } else {
+            self.one_of_name = ::std::option::Option::Some(UpdateUserParams_oneof_one_of_name::name(::std::string::String::new()));
+        }
+        match self.one_of_name {
+            ::std::option::Option::Some(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_oneof_one_of_email::email(_)) = self.one_of_email {
+        } else {
+            self.one_of_email = ::std::option::Option::Some(UpdateUserParams_oneof_one_of_email::email(::std::string::String::new()));
+        }
+        match self.one_of_email {
+            ::std::option::Option::Some(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_oneof_one_of_workspace::workspace(_)) = self.one_of_workspace {
+        } else {
+            self.one_of_workspace = ::std::option::Option::Some(UpdateUserParams_oneof_one_of_workspace::workspace(::std::string::String::new()));
+        }
+        match self.one_of_workspace {
+            ::std::option::Option::Some(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_oneof_one_of_password::password(_)) = self.one_of_password {
+        } else {
+            self.one_of_password = ::std::option::Option::Some(UpdateUserParams_oneof_one_of_password::password(::std::string::String::new()));
+        }
+        match self.one_of_password {
+            ::std::option::Option::Some(UpdateUserParams_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(UpdateUserParams_oneof_one_of_password::password(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for UpdateUserParams {
+    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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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(UpdateUserParams_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 {
+                &UpdateUserParams_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 {
+                &UpdateUserParams_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 {
+                &UpdateUserParams_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 {
+                &UpdateUserParams_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 {
+                &UpdateUserParams_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 {
+                &UpdateUserParams_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 {
+                &UpdateUserParams_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 {
+                &UpdateUserParams_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() -> UpdateUserParams {
+        UpdateUserParams::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: &UpdateUserParams| { &m.id },
+                |m: &mut UpdateUserParams| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "name",
+                UpdateUserParams::has_name,
+                UpdateUserParams::get_name,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "email",
+                UpdateUserParams::has_email,
+                UpdateUserParams::get_email,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "workspace",
+                UpdateUserParams::has_workspace,
+                UpdateUserParams::get_workspace,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "password",
+                UpdateUserParams::has_password,
+                UpdateUserParams::get_password,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdateUserParams>(
+                "UpdateUserParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UpdateUserParams {
+        static instance: ::protobuf::rt::LazyV2<UpdateUserParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UpdateUserParams::new)
+    }
+}
+
+impl ::protobuf::Clear for UpdateUserParams {
+    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 UpdateUserParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for UpdateUserParams {
+    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\
+    B\x12\n\x10one_of_workspaceB\x11\n\x0fone_of_password\"\xd4\x01\n\x10Upd\
+    ateUserParams\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x14\n\x04nam\
+    e\x18\x02\x20\x01(\tH\0R\x04name\x12\x16\n\x05email\x18\x03\x20\x01(\tH\
+    \x01R\x05email\x12\x1e\n\tworkspace\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_emailB\x12\n\x10one_of_workspaceB\x11\n\x0fone_\
+    of_passwordJ\xc0\x06\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\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\x1d2\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\
+    \x06\x1d#\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06$-\n\x0c\n\x05\x04\0\
+    \x02\x03\x03\x12\x03\x0601\n\x0b\n\x04\x04\0\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./\n\n\n\x02\x04\x01\x12\x04\t\0\x0f\x01\n\n\n\x03\x04\x01\x01\
+    \x12\x03\t\x08\x18\n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\x12\n\x0c\n\
+    \x05\x04\x01\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\
+    \x03\n\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x10\x11\n\x0b\n\x04\
+    \x04\x01\x08\0\x12\x03\x0b\x04*\n\x0c\n\x05\x04\x01\x08\0\x01\x12\x03\
+    \x0b\n\x15\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x0b\x18(\n\x0c\n\x05\x04\
+    \x01\x02\x01\x05\x12\x03\x0b\x18\x1e\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\
+    \x03\x0b\x1f#\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x0b&'\n\x0b\n\x04\
+    \x04\x01\x08\x01\x12\x03\x0c\x04,\n\x0c\n\x05\x04\x01\x08\x01\x01\x12\
+    \x03\x0c\n\x16\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\x0c\x19*\n\x0c\n\x05\
+    \x04\x01\x02\x02\x05\x12\x03\x0c\x19\x1f\n\x0c\n\x05\x04\x01\x02\x02\x01\
+    \x12\x03\x0c\x20%\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\x0c()\n\x0b\n\
+    \x04\x04\x01\x08\x02\x12\x03\r\x044\n\x0c\n\x05\x04\x01\x08\x02\x01\x12\
+    \x03\r\n\x1a\n\x0b\n\x04\x04\x01\x02\x03\x12\x03\r\x1d2\n\x0c\n\x05\x04\
+    \x01\x02\x03\x05\x12\x03\r\x1d#\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\
+    \r$-\n\x0c\n\x05\x04\x01\x02\x03\x03\x12\x03\r01\n\x0b\n\x04\x04\x01\x08\
+    \x03\x12\x03\x0e\x042\n\x0c\n\x05\x04\x01\x08\x03\x01\x12\x03\x0e\n\x19\
+    \n\x0b\n\x04\x04\x01\x02\x04\x12\x03\x0e\x1c0\n\x0c\n\x05\x04\x01\x02\
+    \x04\x05\x12\x03\x0e\x1c\"\n\x0c\n\x05\x04\x01\x02\x04\x01\x12\x03\x0e#+\
+    \n\x0c\n\x05\x04\x01\x02\x04\x03\x12\x03\x0e./b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -7,3 +7,10 @@ message UpdateUserRequest {
     oneof one_of_workspace { string workspace = 4; };
     oneof one_of_password { string password = 5; };
 }
+message UpdateUserParams {
+    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; };
+}

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

@@ -19,6 +19,7 @@ diesel = {version = "1.4.7", features = ["sqlite"]}
 diesel_derives = {version = "1.4.1", features = ["sqlite"]}
 futures-core = { version = "0.3", default-features = false }
 pin-project = "1.0.0"
+flowy-net = { path = "../flowy-net" }
 
 lazy_static = "1.4.0"
 serde = { version = "1.0", features = ["derive"] }

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

@@ -3,7 +3,7 @@ pub use workspace_query::*;
 pub use workspace_update::*;
 pub use workspace_user_detail::*;
 
-pub(crate) mod parser;
+pub mod parser;
 mod workspace_create;
 mod workspace_delete;
 mod workspace_query;

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

@@ -15,9 +15,16 @@ pub struct CreateWorkspaceRequest {
     pub desc: String,
 }
 
+#[derive(ProtoBuf, Default)]
 pub struct CreateWorkspaceParams {
+    #[pb(index = 1)]
     pub name: String,
+
+    #[pb(index = 2)]
     pub desc: String,
+
+    #[pb(index = 3, one_of)]
+    pub user_id: Option<String>,
 }
 
 impl TryInto<CreateWorkspaceParams> for CreateWorkspaceRequest {
@@ -33,6 +40,7 @@ impl TryInto<CreateWorkspaceParams> for CreateWorkspaceRequest {
         Ok(CreateWorkspaceParams {
             name: name.0,
             desc: self.desc,
+            user_id: None,
         })
     }
 }
@@ -52,6 +60,17 @@ pub struct Workspace {
     pub apps: RepeatedApp,
 }
 
+impl Workspace {
+    pub fn new(id: String, name: String, desc: String) -> Self {
+        Self {
+            id,
+            name,
+            desc,
+            apps: RepeatedApp::default(),
+        }
+    }
+}
+
 #[derive(PartialEq, Debug, Default, ProtoBuf)]
 pub struct Workspaces {
     #[pb(index = 1)]

+ 2 - 0
rust-lib/flowy-workspace/src/entities/workspace/workspace_delete.rs

@@ -11,7 +11,9 @@ pub struct DeleteWorkspaceRequest {
     workspace_id: String,
 }
 
+#[derive(ProtoBuf, Default)]
 pub struct DeleteWorkspaceParams {
+    #[pb(index = 1)]
     workspace_id: String,
 }
 

+ 4 - 0
rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs

@@ -11,8 +11,12 @@ pub struct QueryWorkspaceRequest {
     pub read_apps: bool,
 }
 
+#[derive(ProtoBuf, Default)]
 pub struct QueryWorkspaceParams {
+    #[pb(index = 1)]
     pub workspace_id: String,
+
+    #[pb(index = 2)]
     pub read_apps: bool,
 }
 

+ 6 - 0
rust-lib/flowy-workspace/src/entities/workspace/workspace_update.rs

@@ -16,9 +16,15 @@ pub struct UpdateWorkspaceRequest {
     desc: Option<String>,
 }
 
+#[derive(ProtoBuf, Default)]
 pub struct UpdateWorkspaceParams {
+    #[pb(index = 1)]
     pub id: String,
+
+    #[pb(index = 2, one_of)]
     pub name: Option<String>,
+
+    #[pb(index = 3, one_of)]
     pub desc: Option<String>,
 }
 

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

@@ -65,12 +65,23 @@ pub enum WsErrCode {
 
     #[display(fmt = "User not login yet")]
     UserNotLoginYet      = 103,
+
+    #[display(fmt = "Server error")]
+    ServerError          = 1000,
 }
 
 impl std::default::Default for WsErrCode {
     fn default() -> Self { WsErrCode::Unknown }
 }
 
+impl std::convert::From<flowy_net::errors::ServerError> for WorkspaceError {
+    fn from(error: flowy_net::errors::ServerError) -> Self {
+        ErrorBuilder::new(WsErrCode::ServerError)
+            .error(error.msg)
+            .build()
+    }
+}
+
 impl std::convert::From<flowy_database::result::Error> for WorkspaceError {
     fn from(error: flowy_database::result::Error) -> Self {
         ErrorBuilder::new(WsErrCode::WorkspaceDatabaseError)

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

@@ -1,15 +1,16 @@
+mod handlers;
+mod observable;
+mod services;
+mod sql_tables;
+
 pub mod entities;
 pub mod errors;
 pub mod event;
-mod handlers;
 pub mod module;
-mod sql_tables;
+pub mod protobuf;
 
 #[macro_use]
 mod macros;
-mod observable;
-mod protobuf;
-mod services;
 
 #[macro_use]
 extern crate flowy_database;
@@ -18,5 +19,10 @@ extern crate flowy_database;
 // extern crate flowy_dispatch;
 
 pub mod prelude {
-    pub use crate::{errors::*, module::*, services::*};
+    pub use crate::{
+        entities::{app::*, view::*, workspace::*},
+        errors::*,
+        module::*,
+        services::*,
+    };
 }

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

@@ -229,6 +229,7 @@ pub enum WsErrCode {
     WorkspaceDatabaseError = 101,
     UserInternalError = 102,
     UserNotLoginYet = 103,
+    ServerError = 1000,
 }
 
 impl ::protobuf::ProtobufEnum for WsErrCode {
@@ -252,6 +253,7 @@ impl ::protobuf::ProtobufEnum for WsErrCode {
             101 => ::std::option::Option::Some(WsErrCode::WorkspaceDatabaseError),
             102 => ::std::option::Option::Some(WsErrCode::UserInternalError),
             103 => ::std::option::Option::Some(WsErrCode::UserNotLoginYet),
+            1000 => ::std::option::Option::Some(WsErrCode::ServerError),
             _ => ::std::option::Option::None
         }
     }
@@ -272,6 +274,7 @@ impl ::protobuf::ProtobufEnum for WsErrCode {
             WsErrCode::WorkspaceDatabaseError,
             WsErrCode::UserInternalError,
             WsErrCode::UserNotLoginYet,
+            WsErrCode::ServerError,
         ];
         values
     }
@@ -302,51 +305,53 @@ impl ::protobuf::reflect::ProtobufValue for WsErrCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"B\n\x0eWorkspaceError\x12\x1e\n\x04code\x18\x01\x20\
     \x01(\x0e2\n.WsErrCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03\
-    msg*\xc5\x02\n\tWsErrCode\x12\x0b\n\x07Unknown\x10\0\x12\x18\n\x14Worksp\
+    msg*\xd7\x02\n\tWsErrCode\x12\x0b\n\x07Unknown\x10\0\x12\x18\n\x14Worksp\
     aceNameInvalid\x10\x01\x12\x16\n\x12WorkspaceIdInvalid\x10\x02\x12\x18\n\
     \x14AppColorStyleInvalid\x10\x03\x12\x10\n\x0cAppIdInvalid\x10\n\x12\x12\
     \n\x0eAppNameInvalid\x10\x0b\x12\x13\n\x0fViewNameInvalid\x10\x14\x12\
     \x18\n\x14ViewThumbnailInvalid\x10\x15\x12\x11\n\rViewIdInvalid\x10\x16\
     \x12\x13\n\x0fViewDescInvalid\x10\x17\x12\x1a\n\x16DatabaseConnectionFai\
     l\x10d\x12\x1a\n\x16WorkspaceDatabaseError\x10e\x12\x15\n\x11UserInterna\
-    lError\x10f\x12\x13\n\x0fUserNotLoginYet\x10gJ\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\x16\n\x0b\n\x04\x04\0\x02\0\
-    \x12\x03\x03\x04\x17\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\r\n\x0c\
-    \n\x05\x04\0\x02\0\x01\x12\x03\x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\x03\
-    \x12\x03\x03\x15\x16\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\
-    \x0e\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\x16\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\x15\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x0c\x04\x18\n\x0c\n\x05\x05\0\
-    \x02\x05\x01\x12\x03\x0c\x04\x12\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\
-    \x0c\x15\x17\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\x1e\n\x0c\n\x05\x05\0\
-    \x02\x07\x01\x12\x03\x0e\x04\x18\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\
-    \x0e\x1b\x1d\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\x04\x17\n\x0c\n\x05\
-    \x05\0\x02\x08\x01\x12\x03\x0f\x04\x11\n\x0c\n\x05\x05\0\x02\x08\x02\x12\
-    \x03\x0f\x14\x16\n\x0b\n\x04\x05\0\x02\t\x12\x03\x10\x04\x19\n\x0c\n\x05\
-    \x05\0\x02\t\x01\x12\x03\x10\x04\x13\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\
-    \x10\x16\x18\n\x0b\n\x04\x05\0\x02\n\x12\x03\x11\x04!\n\x0c\n\x05\x05\0\
-    \x02\n\x01\x12\x03\x11\x04\x1a\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\
-    \x1d\x20\n\x0b\n\x04\x05\0\x02\x0b\x12\x03\x12\x04!\n\x0c\n\x05\x05\0\
-    \x02\x0b\x01\x12\x03\x12\x04\x1a\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\
-    \x12\x1d\x20\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x13\x04\x1c\n\x0c\n\x05\
-    \x05\0\x02\x0c\x01\x12\x03\x13\x04\x15\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\
-    \x03\x13\x18\x1b\n\x0b\n\x04\x05\0\x02\r\x12\x03\x14\x04\x1a\n\x0c\n\x05\
-    \x05\0\x02\r\x01\x12\x03\x14\x04\x13\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\
-    \x14\x16\x19b\x06proto3\
+    lError\x10f\x12\x13\n\x0fUserNotLoginYet\x10g\x12\x10\n\x0bServerError\
+    \x10\xe8\x07J\x97\x06\n\x06\x12\x04\0\0\x16\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\x17\n\x0c\n\x05\
+    \x04\0\x02\0\x06\x12\x03\x03\x04\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\
+    \x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x15\x16\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\x16\
+    \x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x0e\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\x16\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\x15\n\x0b\n\x04\x05\0\x02\x05\
+    \x12\x03\x0c\x04\x18\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x12\n\
+    \x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x15\x17\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\x1e\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x18\n\
+    \x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x1b\x1d\n\x0b\n\x04\x05\0\x02\
+    \x08\x12\x03\x0f\x04\x17\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\
+    \x11\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x14\x16\n\x0b\n\x04\x05\0\
+    \x02\t\x12\x03\x10\x04\x19\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\
+    \x13\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x16\x18\n\x0b\n\x04\x05\0\
+    \x02\n\x12\x03\x11\x04!\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\x1a\
+    \n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x1d\x20\n\x0b\n\x04\x05\0\x02\
+    \x0b\x12\x03\x12\x04!\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\x04\x1a\
+    \n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x1d\x20\n\x0b\n\x04\x05\0\x02\
+    \x0c\x12\x03\x13\x04\x1c\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x13\x04\
+    \x15\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\x18\x1b\n\x0b\n\x04\x05\0\
+    \x02\r\x12\x03\x14\x04\x1a\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x14\x04\
+    \x13\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x16\x19\n\x0b\n\x04\x05\0\
+    \x02\x0e\x12\x03\x15\x04\x17\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\x15\
+    \x04\x0f\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\x15\x12\x16b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 326 - 30
rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs

@@ -224,6 +224,289 @@ impl ::protobuf::reflect::ProtobufValue for CreateWorkspaceRequest {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct CreateWorkspaceParams {
+    // message fields
+    pub name: ::std::string::String,
+    pub desc: ::std::string::String,
+    // message oneof groups
+    pub one_of_user_id: ::std::option::Option<CreateWorkspaceParams_oneof_one_of_user_id>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CreateWorkspaceParams {
+    fn default() -> &'a CreateWorkspaceParams {
+        <CreateWorkspaceParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum CreateWorkspaceParams_oneof_one_of_user_id {
+    user_id(::std::string::String),
+}
+
+impl CreateWorkspaceParams {
+    pub fn new() -> CreateWorkspaceParams {
+        ::std::default::Default::default()
+    }
+
+    // string name = 1;
+
+
+    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 = 2;
+
+
+    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())
+    }
+
+    // string user_id = 3;
+
+
+    pub fn get_user_id(&self) -> &str {
+        match self.one_of_user_id {
+            ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_user_id(&mut self) {
+        self.one_of_user_id = ::std::option::Option::None;
+    }
+
+    pub fn has_user_id(&self) -> bool {
+        match self.one_of_user_id {
+            ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_user_id(&mut self, v: ::std::string::String) {
+        self.one_of_user_id = ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_user_id(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(_)) = self.one_of_user_id {
+        } else {
+            self.one_of_user_id = ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(::std::string::String::new()));
+        }
+        match self.one_of_user_id {
+            ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_user_id(&mut self) -> ::std::string::String {
+        if self.has_user_id() {
+            match self.one_of_user_id.take() {
+                ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for CreateWorkspaceParams {
+    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.name)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.desc)?;
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    self.one_of_user_id = ::std::option::Option::Some(CreateWorkspaceParams_oneof_one_of_user_id::user_id(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.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.name);
+        }
+        if !self.desc.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.desc);
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_user_id {
+            match v {
+                &CreateWorkspaceParams_oneof_one_of_user_id::user_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(3, &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.name.is_empty() {
+            os.write_string(1, &self.name)?;
+        }
+        if !self.desc.is_empty() {
+            os.write_string(2, &self.desc)?;
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_user_id {
+            match v {
+                &CreateWorkspaceParams_oneof_one_of_user_id::user_id(ref v) => {
+                    os.write_string(3, 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() -> CreateWorkspaceParams {
+        CreateWorkspaceParams::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>(
+                "name",
+                |m: &CreateWorkspaceParams| { &m.name },
+                |m: &mut CreateWorkspaceParams| { &mut m.name },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "desc",
+                |m: &CreateWorkspaceParams| { &m.desc },
+                |m: &mut CreateWorkspaceParams| { &mut m.desc },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "user_id",
+                CreateWorkspaceParams::has_user_id,
+                CreateWorkspaceParams::get_user_id,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateWorkspaceParams>(
+                "CreateWorkspaceParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CreateWorkspaceParams {
+        static instance: ::protobuf::rt::LazyV2<CreateWorkspaceParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CreateWorkspaceParams::new)
+    }
+}
+
+impl ::protobuf::Clear for CreateWorkspaceParams {
+    fn clear(&mut self) {
+        self.name.clear();
+        self.desc.clear();
+        self.one_of_user_id = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CreateWorkspaceParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CreateWorkspaceParams {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct Workspace {
     // message fields
@@ -693,36 +976,49 @@ impl ::protobuf::reflect::ProtobufValue for Workspaces {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x16workspace_create.proto\x1a\x10app_create.proto\"@\n\x16CreateWorks\
     paceRequest\x12\x12\n\x04name\x18\x01\x20\x01(\tR\x04name\x12\x12\n\x04d\
-    esc\x18\x02\x20\x01(\tR\x04desc\"e\n\tWorkspace\x12\x0e\n\x02id\x18\x01\
-    \x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\
-    \n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12\x20\n\x04apps\x18\x04\x20\x01\
-    (\x0b2\x0c.RepeatedAppR\x04apps\".\n\nWorkspaces\x12\x20\n\x05items\x18\
-    \x01\x20\x03(\x0b2\n.WorkspaceR\x05itemsJ\xf4\x03\n\x06\x12\x04\0\0\x0f\
-    \x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1a\n\
-    \n\n\x02\x04\0\x12\x04\x03\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\
-    \x1e\n\x0b\n\x04\x04\0\x02\0\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\0\
-    \x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x0f\n\
-    \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x01\
-    \x12\x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\
-    \x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\
-    \x01\x03\x12\x03\x05\x12\x13\n\n\n\x02\x04\x01\x12\x04\x07\0\x0c\x01\n\n\
-    \n\x03\x04\x01\x01\x12\x03\x07\x08\x11\n\x0b\n\x04\x04\x01\x02\0\x12\x03\
-    \x08\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x08\x04\n\n\x0c\n\x05\
-    \x04\x01\x02\0\x01\x12\x03\x08\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\
-    \x03\x08\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\t\x04\x14\n\x0c\n\
-    \x05\x04\x01\x02\x01\x05\x12\x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\
-    \x12\x03\t\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\t\x12\x13\n\
-    \x0b\n\x04\x04\x01\x02\x02\x12\x03\n\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\
-    \x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\n\x0b\x0f\n\
-    \x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\n\x12\x13\n\x0b\n\x04\x04\x01\x02\
-    \x03\x12\x03\x0b\x04\x19\n\x0c\n\x05\x04\x01\x02\x03\x06\x12\x03\x0b\x04\
-    \x0f\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\x0b\x10\x14\n\x0c\n\x05\x04\
-    \x01\x02\x03\x03\x12\x03\x0b\x17\x18\n\n\n\x02\x04\x02\x12\x04\r\0\x0f\
-    \x01\n\n\n\x03\x04\x02\x01\x12\x03\r\x08\x12\n\x0b\n\x04\x04\x02\x02\0\
-    \x12\x03\x0e\x04!\n\x0c\n\x05\x04\x02\x02\0\x04\x12\x03\x0e\x04\x0c\n\
-    \x0c\n\x05\x04\x02\x02\0\x06\x12\x03\x0e\r\x16\n\x0c\n\x05\x04\x02\x02\0\
-    \x01\x12\x03\x0e\x17\x1c\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0e\x1f\
-    \x20b\x06proto3\
+    esc\x18\x02\x20\x01(\tR\x04desc\"l\n\x15CreateWorkspaceParams\x12\x12\n\
+    \x04name\x18\x01\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x02\x20\x01(\
+    \tR\x04desc\x12\x19\n\x07user_id\x18\x03\x20\x01(\tH\0R\x06userIdB\x10\n\
+    \x0eone_of_user_id\"e\n\tWorkspace\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
+    \x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\
+    \x18\x03\x20\x01(\tR\x04desc\x12\x20\n\x04apps\x18\x04\x20\x01(\x0b2\x0c\
+    .RepeatedAppR\x04apps\".\n\nWorkspaces\x12\x20\n\x05items\x18\x01\x20\
+    \x03(\x0b2\n.WorkspaceR\x05itemsJ\xcc\x05\n\x06\x12\x04\0\0\x14\x01\n\
+    \x08\n\x01\x0c\x12\x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1a\n\n\n\
+    \x02\x04\0\x12\x04\x03\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\x1e\
+    \n\x0b\n\x04\x04\0\x02\0\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\0\x05\
+    \x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x0f\n\x0c\
+    \n\x05\x04\0\x02\0\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x01\x12\
+    \x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\x0c\n\
+    \x05\x04\0\x02\x01\x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\
+    \x12\x03\x05\x12\x13\n\n\n\x02\x04\x01\x12\x04\x07\0\x0b\x01\n\n\n\x03\
+    \x04\x01\x01\x12\x03\x07\x08\x1d\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x08\
+    \x04\x14\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\
+    \x01\x02\0\x01\x12\x03\x08\x0b\x0f\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\
+    \x08\x12\x13\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\t\x04\x14\n\x0c\n\x05\
+    \x04\x01\x02\x01\x05\x12\x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\
+    \x03\t\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\t\x12\x13\n\x0b\n\
+    \x04\x04\x01\x08\0\x12\x03\n\x040\n\x0c\n\x05\x04\x01\x08\0\x01\x12\x03\
+    \n\n\x18\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\n\x1b.\n\x0c\n\x05\x04\x01\
+    \x02\x02\x05\x12\x03\n\x1b!\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\n\")\
+    \n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\n,-\n\n\n\x02\x04\x02\x12\x04\
+    \x0c\0\x11\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0c\x08\x11\n\x0b\n\x04\x04\
+    \x02\x02\0\x12\x03\r\x04\x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\r\x04\
+    \n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\r\x0b\r\n\x0c\n\x05\x04\x02\x02\
+    \0\x03\x12\x03\r\x10\x11\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\x0e\x04\x14\
+    \n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x0e\x04\n\n\x0c\n\x05\x04\x02\
+    \x02\x01\x01\x12\x03\x0e\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\
+    \x0e\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\x0f\x04\x14\n\x0c\n\x05\
+    \x04\x02\x02\x02\x05\x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x02\x02\x02\x01\
+    \x12\x03\x0f\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\x03\x0f\x12\x13\
+    \n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x10\x04\x19\n\x0c\n\x05\x04\x02\x02\
+    \x03\x06\x12\x03\x10\x04\x0f\n\x0c\n\x05\x04\x02\x02\x03\x01\x12\x03\x10\
+    \x10\x14\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x10\x17\x18\n\n\n\x02\
+    \x04\x03\x12\x04\x12\0\x14\x01\n\n\n\x03\x04\x03\x01\x12\x03\x12\x08\x12\
+    \n\x0b\n\x04\x04\x03\x02\0\x12\x03\x13\x04!\n\x0c\n\x05\x04\x03\x02\0\
+    \x04\x12\x03\x13\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\x06\x12\x03\x13\r\x16\
+    \n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x13\x17\x1c\n\x0c\n\x05\x04\x03\
+    \x02\0\x03\x12\x03\x13\x1f\x20b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 170 - 6
rust-lib/flowy-workspace/src/protobuf/model/workspace_delete.rs

@@ -182,14 +182,178 @@ impl ::protobuf::reflect::ProtobufValue for DeleteWorkspaceRequest {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct DeleteWorkspaceParams {
+    // message fields
+    pub workspace_id: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a DeleteWorkspaceParams {
+    fn default() -> &'a DeleteWorkspaceParams {
+        <DeleteWorkspaceParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl DeleteWorkspaceParams {
+    pub fn new() -> DeleteWorkspaceParams {
+        ::std::default::Default::default()
+    }
+
+    // string workspace_id = 1;
+
+
+    pub fn get_workspace_id(&self) -> &str {
+        &self.workspace_id
+    }
+    pub fn clear_workspace_id(&mut self) {
+        self.workspace_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_workspace_id(&mut self, v: ::std::string::String) {
+        self.workspace_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_workspace_id(&mut self) -> &mut ::std::string::String {
+        &mut self.workspace_id
+    }
+
+    // Take field
+    pub fn take_workspace_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.workspace_id, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for DeleteWorkspaceParams {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace_id)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.workspace_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.workspace_id);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.workspace_id.is_empty() {
+            os.write_string(1, &self.workspace_id)?;
+        }
+        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() -> DeleteWorkspaceParams {
+        DeleteWorkspaceParams::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "workspace_id",
+                |m: &DeleteWorkspaceParams| { &m.workspace_id },
+                |m: &mut DeleteWorkspaceParams| { &mut m.workspace_id },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<DeleteWorkspaceParams>(
+                "DeleteWorkspaceParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static DeleteWorkspaceParams {
+        static instance: ::protobuf::rt::LazyV2<DeleteWorkspaceParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(DeleteWorkspaceParams::new)
+    }
+}
+
+impl ::protobuf::Clear for DeleteWorkspaceParams {
+    fn clear(&mut self) {
+        self.workspace_id.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for DeleteWorkspaceParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for DeleteWorkspaceParams {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x16workspace_delete.proto\";\n\x16DeleteWorkspaceRequest\x12!\n\x0cwo\
-    rkspace_id\x18\x01\x20\x01(\tR\x0bworkspaceIdJa\n\x06\x12\x04\0\0\x04\
-    \x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\
-    \x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x1e\n\x0b\n\x04\x04\0\x02\0\x12\
-    \x03\x03\x04\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\x1bb\x06proto3\
+    rkspace_id\x18\x01\x20\x01(\tR\x0bworkspaceId\":\n\x15DeleteWorkspacePar\
+    ams\x12!\n\x0cworkspace_id\x18\x01\x20\x01(\tR\x0bworkspaceIdJ\xb0\x01\n\
+    \x06\x12\x04\0\0\x07\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\
+    \x12\x04\x02\0\x04\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x1e\n\x0b\n\
+    \x04\x04\0\x02\0\x12\x03\x03\x04\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\n\n\x02\x04\x01\x12\x04\x05\0\x07\
+    \x01\n\n\n\x03\x04\x01\x01\x12\x03\x05\x08\x1d\n\x0b\n\x04\x04\x01\x02\0\
+    \x12\x03\x06\x04\x1c\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x06\x04\n\n\
+    \x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x06\x0b\x17\n\x0c\n\x05\x04\x01\x02\
+    \0\x03\x12\x03\x06\x1a\x1bb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -217,17 +217,220 @@ impl ::protobuf::reflect::ProtobufValue for QueryWorkspaceRequest {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct QueryWorkspaceParams {
+    // message fields
+    pub workspace_id: ::std::string::String,
+    pub read_apps: bool,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a QueryWorkspaceParams {
+    fn default() -> &'a QueryWorkspaceParams {
+        <QueryWorkspaceParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl QueryWorkspaceParams {
+    pub fn new() -> QueryWorkspaceParams {
+        ::std::default::Default::default()
+    }
+
+    // string workspace_id = 1;
+
+
+    pub fn get_workspace_id(&self) -> &str {
+        &self.workspace_id
+    }
+    pub fn clear_workspace_id(&mut self) {
+        self.workspace_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_workspace_id(&mut self, v: ::std::string::String) {
+        self.workspace_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_workspace_id(&mut self) -> &mut ::std::string::String {
+        &mut self.workspace_id
+    }
+
+    // Take field
+    pub fn take_workspace_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.workspace_id, ::std::string::String::new())
+    }
+
+    // bool read_apps = 2;
+
+
+    pub fn get_read_apps(&self) -> bool {
+        self.read_apps
+    }
+    pub fn clear_read_apps(&mut self) {
+        self.read_apps = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_read_apps(&mut self, v: bool) {
+        self.read_apps = v;
+    }
+}
+
+impl ::protobuf::Message for QueryWorkspaceParams {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace_id)?;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.read_apps = tmp;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.workspace_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.workspace_id);
+        }
+        if self.read_apps != false {
+            my_size += 2;
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.workspace_id.is_empty() {
+            os.write_string(1, &self.workspace_id)?;
+        }
+        if self.read_apps != false {
+            os.write_bool(2, self.read_apps)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> QueryWorkspaceParams {
+        QueryWorkspaceParams::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "workspace_id",
+                |m: &QueryWorkspaceParams| { &m.workspace_id },
+                |m: &mut QueryWorkspaceParams| { &mut m.workspace_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "read_apps",
+                |m: &QueryWorkspaceParams| { &m.read_apps },
+                |m: &mut QueryWorkspaceParams| { &mut m.read_apps },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<QueryWorkspaceParams>(
+                "QueryWorkspaceParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static QueryWorkspaceParams {
+        static instance: ::protobuf::rt::LazyV2<QueryWorkspaceParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(QueryWorkspaceParams::new)
+    }
+}
+
+impl ::protobuf::Clear for QueryWorkspaceParams {
+    fn clear(&mut self) {
+        self.workspace_id.clear();
+        self.read_apps = false;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for QueryWorkspaceParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for QueryWorkspaceParams {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x15workspace_query.proto\"W\n\x15QueryWorkspaceRequest\x12!\n\x0cwork\
     space_id\x18\x01\x20\x01(\tR\x0bworkspaceId\x12\x1b\n\tread_apps\x18\x02\
-    \x20\x01(\x08R\x08readAppsJ\x98\x01\n\x06\x12\x04\0\0\x05\x01\n\x08\n\
-    \x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\
-    \x04\0\x01\x12\x03\x02\x08\x1d\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\
-    \x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\
-    \0\x01\x12\x03\x03\x0b\x17\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x1a\
-    \x1b\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x17\n\x0c\n\x05\x04\0\x02\
-    \x01\x05\x12\x03\x04\x04\x08\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\t\
-    \x12\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x15\x16b\x06proto3\
+    \x20\x01(\x08R\x08readApps\"V\n\x14QueryWorkspaceParams\x12!\n\x0cworksp\
+    ace_id\x18\x01\x20\x01(\tR\x0bworkspaceId\x12\x1b\n\tread_apps\x18\x02\
+    \x20\x01(\x08R\x08readAppsJ\x9e\x02\n\x06\x12\x04\0\0\t\x01\n\x08\n\x01\
+    \x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\
+    \0\x01\x12\x03\x02\x08\x1d\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x1c\n\
+    \x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\
+    \x12\x03\x03\x0b\x17\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x1a\x1b\n\
+    \x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x17\n\x0c\n\x05\x04\0\x02\x01\
+    \x05\x12\x03\x04\x04\x08\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\t\x12\
+    \n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x15\x16\n\n\n\x02\x04\x01\x12\
+    \x04\x06\0\t\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x1c\n\x0b\n\x04\
+    \x04\x01\x02\0\x12\x03\x07\x04\x1c\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\
+    \x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\x17\n\x0c\n\x05\
+    \x04\x01\x02\0\x03\x12\x03\x07\x1a\x1b\n\x0b\n\x04\x04\x01\x02\x01\x12\
+    \x03\x08\x04\x17\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x08\x04\x08\n\
+    \x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x08\t\x12\n\x0c\n\x05\x04\x01\x02\
+    \x01\x03\x12\x03\x08\x15\x16b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 339 - 2
rust-lib/flowy-workspace/src/protobuf/model/workspace_update.rs

@@ -345,11 +345,336 @@ impl ::protobuf::reflect::ProtobufValue for UpdateWorkspaceRequest {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct UpdateWorkspaceParams {
+    // message fields
+    pub id: ::std::string::String,
+    // message oneof groups
+    pub one_of_name: ::std::option::Option<UpdateWorkspaceParams_oneof_one_of_name>,
+    pub one_of_desc: ::std::option::Option<UpdateWorkspaceParams_oneof_one_of_desc>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UpdateWorkspaceParams {
+    fn default() -> &'a UpdateWorkspaceParams {
+        <UpdateWorkspaceParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateWorkspaceParams_oneof_one_of_name {
+    name(::std::string::String),
+}
+
+#[derive(Clone,PartialEq,Debug)]
+pub enum UpdateWorkspaceParams_oneof_one_of_desc {
+    desc(::std::string::String),
+}
+
+impl UpdateWorkspaceParams {
+    pub fn new() -> UpdateWorkspaceParams {
+        ::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(UpdateWorkspaceParams_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(UpdateWorkspaceParams_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(UpdateWorkspaceParams_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(UpdateWorkspaceParams_oneof_one_of_name::name(_)) = self.one_of_name {
+        } else {
+            self.one_of_name = ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_name::name(::std::string::String::new()));
+        }
+        match self.one_of_name {
+            ::std::option::Option::Some(UpdateWorkspaceParams_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(UpdateWorkspaceParams_oneof_one_of_name::name(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+
+    // string desc = 3;
+
+
+    pub fn get_desc(&self) -> &str {
+        match self.one_of_desc {
+            ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(ref v)) => v,
+            _ => "",
+        }
+    }
+    pub fn clear_desc(&mut self) {
+        self.one_of_desc = ::std::option::Option::None;
+    }
+
+    pub fn has_desc(&self) -> bool {
+        match self.one_of_desc {
+            ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(..)) => true,
+            _ => false,
+        }
+    }
+
+    // Param is passed by value, moved
+    pub fn set_desc(&mut self, v: ::std::string::String) {
+        self.one_of_desc = ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(v))
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_desc(&mut self) -> &mut ::std::string::String {
+        if let ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(_)) = self.one_of_desc {
+        } else {
+            self.one_of_desc = ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(::std::string::String::new()));
+        }
+        match self.one_of_desc {
+            ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(ref mut v)) => v,
+            _ => panic!(),
+        }
+    }
+
+    // Take field
+    pub fn take_desc(&mut self) -> ::std::string::String {
+        if self.has_desc() {
+            match self.one_of_desc.take() {
+                ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
+    }
+}
+
+impl ::protobuf::Message for UpdateWorkspaceParams {
+    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(UpdateWorkspaceParams_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_desc = ::std::option::Option::Some(UpdateWorkspaceParams_oneof_one_of_desc::desc(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 {
+                &UpdateWorkspaceParams_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_desc {
+            match v {
+                &UpdateWorkspaceParams_oneof_one_of_desc::desc(ref v) => {
+                    my_size += ::protobuf::rt::string_size(3, &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 {
+                &UpdateWorkspaceParams_oneof_one_of_name::name(ref v) => {
+                    os.write_string(2, v)?;
+                },
+            };
+        }
+        if let ::std::option::Option::Some(ref v) = self.one_of_desc {
+            match v {
+                &UpdateWorkspaceParams_oneof_one_of_desc::desc(ref v) => {
+                    os.write_string(3, 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() -> UpdateWorkspaceParams {
+        UpdateWorkspaceParams::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: &UpdateWorkspaceParams| { &m.id },
+                |m: &mut UpdateWorkspaceParams| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "name",
+                UpdateWorkspaceParams::has_name,
+                UpdateWorkspaceParams::get_name,
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
+                "desc",
+                UpdateWorkspaceParams::has_desc,
+                UpdateWorkspaceParams::get_desc,
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UpdateWorkspaceParams>(
+                "UpdateWorkspaceParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UpdateWorkspaceParams {
+        static instance: ::protobuf::rt::LazyV2<UpdateWorkspaceParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UpdateWorkspaceParams::new)
+    }
+}
+
+impl ::protobuf::Clear for UpdateWorkspaceParams {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.one_of_name = ::std::option::Option::None;
+        self.one_of_desc = ::std::option::Option::None;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for UpdateWorkspaceParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for UpdateWorkspaceParams {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x16workspace_update.proto\"r\n\x16UpdateWorkspaceRequest\x12\x0e\n\
     \x02id\x18\x01\x20\x01(\tR\x02id\x12\x14\n\x04name\x18\x02\x20\x01(\tH\0\
     R\x04name\x12\x14\n\x04desc\x18\x03\x20\x01(\tH\x01R\x04descB\r\n\x0bone\
-    _of_nameB\r\n\x0bone_of_descJ\x85\x02\n\x06\x12\x04\0\0\x06\x01\n\x08\n\
+    _of_nameB\r\n\x0bone_of_desc\"q\n\x15UpdateWorkspaceParams\x12\x0e\n\x02\
+    id\x18\x01\x20\x01(\tR\x02id\x12\x14\n\x04name\x18\x02\x20\x01(\tH\0R\
+    \x04name\x12\x14\n\x04desc\x18\x03\x20\x01(\tH\x01R\x04descB\r\n\x0bone_\
+    of_nameB\r\n\x0bone_of_descJ\xf8\x03\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\x1e\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\
@@ -361,7 +686,19 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x04\x04\0\x08\x01\x12\x03\x05\x04*\n\x0c\n\x05\x04\0\x08\x01\x01\x12\
     \x03\x05\n\x15\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x18(\n\x0c\n\x05\
     \x04\0\x02\x02\x05\x12\x03\x05\x18\x1e\n\x0c\n\x05\x04\0\x02\x02\x01\x12\
-    \x03\x05\x1f#\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05&'b\x06proto3\
+    \x03\x05\x1f#\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05&'\n\n\n\x02\x04\
+    \x01\x12\x04\x07\0\x0b\x01\n\n\n\x03\x04\x01\x01\x12\x03\x07\x08\x1d\n\
+    \x0b\n\x04\x04\x01\x02\0\x12\x03\x08\x04\x12\n\x0c\n\x05\x04\x01\x02\0\
+    \x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x08\x0b\r\n\
+    \x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x08\x10\x11\n\x0b\n\x04\x04\x01\x08\
+    \0\x12\x03\t\x04*\n\x0c\n\x05\x04\x01\x08\0\x01\x12\x03\t\n\x15\n\x0b\n\
+    \x04\x04\x01\x02\x01\x12\x03\t\x18(\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\
+    \x03\t\x18\x1e\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\t\x1f#\n\x0c\n\
+    \x05\x04\x01\x02\x01\x03\x12\x03\t&'\n\x0b\n\x04\x04\x01\x08\x01\x12\x03\
+    \n\x04*\n\x0c\n\x05\x04\x01\x08\x01\x01\x12\x03\n\n\x15\n\x0b\n\x04\x04\
+    \x01\x02\x02\x12\x03\n\x18(\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\n\
+    \x18\x1e\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\n\x1f#\n\x0c\n\x05\x04\
+    \x01\x02\x02\x03\x12\x03\n&'b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -19,4 +19,5 @@ enum WsErrCode {
     WorkspaceDatabaseError = 101;
     UserInternalError = 102;
     UserNotLoginYet = 103;
+    ServerError = 1000;
 }

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

@@ -5,6 +5,11 @@ message CreateWorkspaceRequest {
     string name = 1;
     string desc = 2;
 }
+message CreateWorkspaceParams {
+    string name = 1;
+    string desc = 2;
+    oneof one_of_user_id { string user_id = 3; };
+}
 message Workspace {
     string id = 1;
     string name = 2;

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

@@ -3,3 +3,6 @@ syntax = "proto3";
 message DeleteWorkspaceRequest {
     string workspace_id = 1;
 }
+message DeleteWorkspaceParams {
+    string workspace_id = 1;
+}

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

@@ -4,3 +4,7 @@ message QueryWorkspaceRequest {
     string workspace_id = 1;
     bool read_apps = 2;
 }
+message QueryWorkspaceParams {
+    string workspace_id = 1;
+    bool read_apps = 2;
+}

+ 5 - 0
rust-lib/flowy-workspace/src/protobuf/proto/workspace_update.proto

@@ -5,3 +5,8 @@ message UpdateWorkspaceRequest {
     oneof one_of_name { string name = 2; };
     oneof one_of_desc { string desc = 3; };
 }
+message UpdateWorkspaceParams {
+    string id = 1;
+    oneof one_of_name { string name = 2; };
+    oneof one_of_desc { string desc = 3; };
+}

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

@@ -7,6 +7,7 @@ use crate::{
     sql_tables::workspace::{WorkspaceSql, WorkspaceTable, WorkspaceTableChangeset},
 };
 use flowy_dispatch::prelude::DispatchFuture;
+use flowy_net::request::HttpRequestBuilder;
 use std::sync::Arc;
 
 pub struct WorkspaceController {
@@ -31,9 +32,11 @@ impl WorkspaceController {
 
     pub async fn create_workspace(
         &self,
-        params: CreateWorkspaceParams,
+        mut params: CreateWorkspaceParams,
     ) -> Result<Workspace, WorkspaceError> {
         let user_id = self.user.user_id()?;
+        params.user_id = Some(user_id.clone());
+
         let workspace_table = WorkspaceTable::new(params, &user_id);
         let workspace: Workspace = workspace_table.clone().into();
         let _ = self.sql.create_workspace(workspace_table)?;
@@ -112,3 +115,27 @@ impl WorkspaceController {
         }
     }
 }
+
+pub async fn create_workspace_request(
+    params: CreateWorkspaceParams,
+    url: &str,
+) -> Result<Workspace, WorkspaceError> {
+    let workspace = HttpRequestBuilder::post(&url.to_owned())
+        .protobuf(params)?
+        .send()
+        .await?
+        .response()?;
+    Ok(workspace)
+}
+
+pub async fn read_workspace_request(
+    params: QueryWorkspaceParams,
+    url: &str,
+) -> Result<Workspace, WorkspaceError> {
+    let workspace = HttpRequestBuilder::get(&url.to_owned())
+        .protobuf(params)?
+        .send()
+        .await?
+        .response()?;
+    Ok(workspace)
+}