Browse Source

add open workspace handler

appflowy 3 năm trước cách đây
mục cha
commit
138998aebd
34 tập tin đã thay đổi với 476 bổ sung474 xóa
  1. 2 2
      app_flowy/lib/user/application/sign_in/sign_in_bloc.dart
  2. 1 0
      app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart
  3. 2 2
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  4. 0 14
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_detail.pb.dart
  5. 1 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_detail.pbjson.dart
  6. 42 16
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pb.dart
  7. 12 6
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbjson.dart
  8. 5 19
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pb.dart
  9. 2 3
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pbjson.dart
  10. 2 0
      backend/src/application.rs
  11. 6 7
      backend/src/workspace_service/app/app.rs
  12. 9 4
      backend/src/workspace_service/workspace/router.rs
  13. 25 53
      backend/src/workspace_service/workspace/workspace.rs
  14. 3 3
      backend/tests/api/helper.rs
  15. 17 20
      backend/tests/api/workspace.rs
  16. 2 1
      rust-lib/flowy-dispatch/tests/api/helper.rs
  17. 1 2
      rust-lib/flowy-sdk/src/deps_resolve/workspace_deps_impl.rs
  18. 0 4
      rust-lib/flowy-user/src/entities/user_detail.rs
  19. 0 25
      rust-lib/flowy-user/src/entities/user_update.rs
  20. 25 71
      rust-lib/flowy-user/src/protobuf/model/user_detail.rs
  21. 0 1
      rust-lib/flowy-user/src/protobuf/proto/user_detail.proto
  22. 1 7
      rust-lib/flowy-user/src/services/user/user_session.rs
  23. 3 3
      rust-lib/flowy-user/src/sql_tables/user.rs
  24. 25 18
      rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs
  25. 0 3
      rust-lib/flowy-workspace/src/entities/workspace/workspace_user_detail.rs
  26. 5 1
      rust-lib/flowy-workspace/src/event.rs
  27. 15 7
      rust-lib/flowy-workspace/src/handlers/workspace_handler.rs
  28. 2 1
      rust-lib/flowy-workspace/src/module.rs
  29. 192 95
      rust-lib/flowy-workspace/src/protobuf/model/workspace_query.rs
  30. 10 55
      rust-lib/flowy-workspace/src/protobuf/model/workspace_user_detail.rs
  31. 4 4
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_query.proto
  32. 1 2
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_user_detail.proto
  33. 48 18
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  34. 13 5
      rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_sql.rs

+ 2 - 2
app_flowy/lib/user/application/sign_in/sign_in_bloc.dart

@@ -43,12 +43,12 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
 
   SignInState stateFromCode(UserError error) {
     switch (error.code) {
-      case UserErrCode.EmailInvalid:
+      case UserErrCode.EmailFormatInvalid:
         return state.copyWith(
             isSubmitting: false,
             emailError: some(error.msg),
             passwordError: none());
-      case UserErrCode.PasswordInvalid:
+      case UserErrCode.PasswordFormatInvalid:
         return state.copyWith(
             isSubmitting: false,
             passwordError: some(error.msg),

+ 1 - 0
app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart

@@ -40,6 +40,7 @@ class WorkspaceRepo {
       {bool readApps = false}) {
     final request = QueryWorkspaceRequest.create()
       ..workspaceId = user.workspace
+      ..user_id = user.id
       ..readApps = readApps;
 
     return WorkspaceEventReadWorkspace(request).send().then((result) {

+ 2 - 2
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -70,12 +70,12 @@ class WorkspaceEventDeleteWorkspace {
 class WorkspaceEventReadAllWorkspace {
     WorkspaceEventReadAllWorkspace();
 
-    Future<Either<Workspaces, WorkspaceError>> send() {
+    Future<Either<RepeatedWorkspace, WorkspaceError>> send() {
      final request = FFIRequest.create()
         ..event = WorkspaceEvent.ReadAllWorkspace.toString();
 
      return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-        (okBytes) => left(Workspaces.fromBuffer(okBytes)),
+        (okBytes) => left(RepeatedWorkspace.fromBuffer(okBytes)),
         (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
       ));
     }

+ 0 - 14
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/user_detail.pb.dart

@@ -19,7 +19,6 @@ class UserDetail extends $pb.GeneratedMessage {
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'email')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
     ..e<UserStatus>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'status', $pb.PbFieldType.OE, defaultOrMaker: UserStatus.Unknown, valueOf: UserStatus.valueOf, enumValues: UserStatus.values)
-    ..aOS(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspace')
     ..hasRequiredFields = false
   ;
 
@@ -29,7 +28,6 @@ class UserDetail extends $pb.GeneratedMessage {
     $core.String? email,
     $core.String? name,
     UserStatus? status,
-    $core.String? workspace,
   }) {
     final _result = create();
     if (id != null) {
@@ -44,9 +42,6 @@ class UserDetail extends $pb.GeneratedMessage {
     if (status != null) {
       _result.status = status;
     }
-    if (workspace != null) {
-      _result.workspace = workspace;
-    }
     return _result;
   }
   factory UserDetail.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -105,14 +100,5 @@ class UserDetail extends $pb.GeneratedMessage {
   $core.bool hasStatus() => $_has(3);
   @$pb.TagNumber(4)
   void clearStatus() => clearField(4);
-
-  @$pb.TagNumber(5)
-  $core.String get workspace => $_getSZ(4);
-  @$pb.TagNumber(5)
-  set workspace($core.String v) { $_setString(4, v); }
-  @$pb.TagNumber(5)
-  $core.bool hasWorkspace() => $_has(4);
-  @$pb.TagNumber(5)
-  void clearWorkspace() => clearField(5);
 }
 

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

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

+ 42 - 16
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pb.dart

@@ -9,24 +9,34 @@ import 'dart:core' as $core;
 
 import 'package:protobuf/protobuf.dart' as $pb;
 
+enum QueryWorkspaceRequest_OneOfWorkspaceId {
+  workspaceId, 
+  notSet
+}
+
 class QueryWorkspaceRequest extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, QueryWorkspaceRequest_OneOfWorkspaceId> _QueryWorkspaceRequest_OneOfWorkspaceIdByTag = {
+    1 : QueryWorkspaceRequest_OneOfWorkspaceId.workspaceId,
+    0 : QueryWorkspaceRequest_OneOfWorkspaceId.notSet
+  };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryWorkspaceRequest', createEmptyInstance: create)
+    ..oo(0, [1])
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspaceId')
-    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'readApps')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'userId')
     ..hasRequiredFields = false
   ;
 
   QueryWorkspaceRequest._() : super();
   factory QueryWorkspaceRequest({
     $core.String? workspaceId,
-    $core.bool? readApps,
+    $core.String? userId,
   }) {
     final _result = create();
     if (workspaceId != null) {
       _result.workspaceId = workspaceId;
     }
-    if (readApps != null) {
-      _result.readApps = readApps;
+    if (userId != null) {
+      _result.userId = userId;
     }
     return _result;
   }
@@ -51,6 +61,9 @@ class QueryWorkspaceRequest extends $pb.GeneratedMessage {
   static QueryWorkspaceRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<QueryWorkspaceRequest>(create);
   static QueryWorkspaceRequest? _defaultInstance;
 
+  QueryWorkspaceRequest_OneOfWorkspaceId whichOneOfWorkspaceId() => _QueryWorkspaceRequest_OneOfWorkspaceIdByTag[$_whichOneof(0)]!;
+  void clearOneOfWorkspaceId() => clearField($_whichOneof(0));
+
   @$pb.TagNumber(1)
   $core.String get workspaceId => $_getSZ(0);
   @$pb.TagNumber(1)
@@ -61,33 +74,43 @@ class QueryWorkspaceRequest extends $pb.GeneratedMessage {
   void clearWorkspaceId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.bool get readApps => $_getBF(1);
+  $core.String get userId => $_getSZ(1);
   @$pb.TagNumber(2)
-  set readApps($core.bool v) { $_setBool(1, v); }
+  set userId($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasReadApps() => $_has(1);
+  $core.bool hasUserId() => $_has(1);
   @$pb.TagNumber(2)
-  void clearReadApps() => clearField(2);
+  void clearUserId() => clearField(2);
+}
+
+enum QueryWorkspaceParams_OneOfWorkspaceId {
+  workspaceId, 
+  notSet
 }
 
 class QueryWorkspaceParams extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, QueryWorkspaceParams_OneOfWorkspaceId> _QueryWorkspaceParams_OneOfWorkspaceIdByTag = {
+    1 : QueryWorkspaceParams_OneOfWorkspaceId.workspaceId,
+    0 : QueryWorkspaceParams_OneOfWorkspaceId.notSet
+  };
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'QueryWorkspaceParams', createEmptyInstance: create)
+    ..oo(0, [1])
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspaceId')
-    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'readApps')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'userId')
     ..hasRequiredFields = false
   ;
 
   QueryWorkspaceParams._() : super();
   factory QueryWorkspaceParams({
     $core.String? workspaceId,
-    $core.bool? readApps,
+    $core.String? userId,
   }) {
     final _result = create();
     if (workspaceId != null) {
       _result.workspaceId = workspaceId;
     }
-    if (readApps != null) {
-      _result.readApps = readApps;
+    if (userId != null) {
+      _result.userId = userId;
     }
     return _result;
   }
@@ -112,6 +135,9 @@ class QueryWorkspaceParams extends $pb.GeneratedMessage {
   static QueryWorkspaceParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<QueryWorkspaceParams>(create);
   static QueryWorkspaceParams? _defaultInstance;
 
+  QueryWorkspaceParams_OneOfWorkspaceId whichOneOfWorkspaceId() => _QueryWorkspaceParams_OneOfWorkspaceIdByTag[$_whichOneof(0)]!;
+  void clearOneOfWorkspaceId() => clearField($_whichOneof(0));
+
   @$pb.TagNumber(1)
   $core.String get workspaceId => $_getSZ(0);
   @$pb.TagNumber(1)
@@ -122,12 +148,12 @@ class QueryWorkspaceParams extends $pb.GeneratedMessage {
   void clearWorkspaceId() => clearField(1);
 
   @$pb.TagNumber(2)
-  $core.bool get readApps => $_getBF(1);
+  $core.String get userId => $_getSZ(1);
   @$pb.TagNumber(2)
-  set readApps($core.bool v) { $_setBool(1, v); }
+  set userId($core.String v) { $_setString(1, v); }
   @$pb.TagNumber(2)
-  $core.bool hasReadApps() => $_has(1);
+  $core.bool hasUserId() => $_has(1);
   @$pb.TagNumber(2)
-  void clearReadApps() => clearField(2);
+  void clearUserId() => clearField(2);
 }
 

+ 12 - 6
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_query.pbjson.dart

@@ -12,21 +12,27 @@ import 'dart:typed_data' as $typed_data;
 const QueryWorkspaceRequest$json = const {
   '1': 'QueryWorkspaceRequest',
   '2': const [
-    const {'1': 'workspace_id', '3': 1, '4': 1, '5': 9, '10': 'workspaceId'},
-    const {'1': 'read_apps', '3': 2, '4': 1, '5': 8, '10': 'readApps'},
+    const {'1': 'workspace_id', '3': 1, '4': 1, '5': 9, '9': 0, '10': 'workspaceId'},
+    const {'1': 'user_id', '3': 2, '4': 1, '5': 9, '10': 'userId'},
+  ],
+  '8': const [
+    const {'1': 'one_of_workspace_id'},
   ],
 };
 
 /// Descriptor for `QueryWorkspaceRequest`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List queryWorkspaceRequestDescriptor = $convert.base64Decode('ChVRdWVyeVdvcmtzcGFjZVJlcXVlc3QSIQoMd29ya3NwYWNlX2lkGAEgASgJUgt3b3Jrc3BhY2VJZBIbCglyZWFkX2FwcHMYAiABKAhSCHJlYWRBcHBz');
+final $typed_data.Uint8List queryWorkspaceRequestDescriptor = $convert.base64Decode('ChVRdWVyeVdvcmtzcGFjZVJlcXVlc3QSIwoMd29ya3NwYWNlX2lkGAEgASgJSABSC3dvcmtzcGFjZUlkEhcKB3VzZXJfaWQYAiABKAlSBnVzZXJJZEIVChNvbmVfb2Zfd29ya3NwYWNlX2lk');
 @$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'},
+    const {'1': 'workspace_id', '3': 1, '4': 1, '5': 9, '9': 0, '10': 'workspaceId'},
+    const {'1': 'user_id', '3': 2, '4': 1, '5': 9, '10': 'userId'},
+  ],
+  '8': const [
+    const {'1': 'one_of_workspace_id'},
   ],
 };
 
 /// Descriptor for `QueryWorkspaceParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List queryWorkspaceParamsDescriptor = $convert.base64Decode('ChRRdWVyeVdvcmtzcGFjZVBhcmFtcxIhCgx3b3Jrc3BhY2VfaWQYASABKAlSC3dvcmtzcGFjZUlkEhsKCXJlYWRfYXBwcxgCIAEoCFIIcmVhZEFwcHM=');
+final $typed_data.Uint8List queryWorkspaceParamsDescriptor = $convert.base64Decode('ChRRdWVyeVdvcmtzcGFjZVBhcmFtcxIjCgx3b3Jrc3BhY2VfaWQYASABKAlIAFILd29ya3NwYWNlSWQSFwoHdXNlcl9pZBgCIAEoCVIGdXNlcklkQhUKE29uZV9vZl93b3Jrc3BhY2VfaWQ=');

+ 5 - 19
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_user_detail.pb.dart

@@ -11,20 +11,15 @@ import 'package:protobuf/protobuf.dart' as $pb;
 
 class CurrentWorkspace extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CurrentWorkspace', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'owner')
-    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspaceId')
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workspaceId')
     ..hasRequiredFields = false
   ;
 
   CurrentWorkspace._() : super();
   factory CurrentWorkspace({
-    $core.String? owner,
     $core.String? workspaceId,
   }) {
     final _result = create();
-    if (owner != null) {
-      _result.owner = owner;
-    }
     if (workspaceId != null) {
       _result.workspaceId = workspaceId;
     }
@@ -52,21 +47,12 @@ class CurrentWorkspace extends $pb.GeneratedMessage {
   static CurrentWorkspace? _defaultInstance;
 
   @$pb.TagNumber(1)
-  $core.String get owner => $_getSZ(0);
+  $core.String get workspaceId => $_getSZ(0);
   @$pb.TagNumber(1)
-  set owner($core.String v) { $_setString(0, v); }
+  set workspaceId($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasOwner() => $_has(0);
+  $core.bool hasWorkspaceId() => $_has(0);
   @$pb.TagNumber(1)
-  void clearOwner() => clearField(1);
-
-  @$pb.TagNumber(2)
-  $core.String get workspaceId => $_getSZ(1);
-  @$pb.TagNumber(2)
-  set workspaceId($core.String v) { $_setString(1, v); }
-  @$pb.TagNumber(2)
-  $core.bool hasWorkspaceId() => $_has(1);
-  @$pb.TagNumber(2)
-  void clearWorkspaceId() => clearField(2);
+  void clearWorkspaceId() => clearField(1);
 }
 

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

@@ -12,10 +12,9 @@ import 'dart:typed_data' as $typed_data;
 const CurrentWorkspace$json = const {
   '1': 'CurrentWorkspace',
   '2': const [
-    const {'1': 'owner', '3': 1, '4': 1, '5': 9, '10': 'owner'},
-    const {'1': 'workspace_id', '3': 2, '4': 1, '5': 9, '10': 'workspaceId'},
+    const {'1': 'workspace_id', '3': 1, '4': 1, '5': 9, '10': 'workspaceId'},
   ],
 };
 
 /// Descriptor for `CurrentWorkspace`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List currentWorkspaceDescriptor = $convert.base64Decode('ChBDdXJyZW50V29ya3NwYWNlEhQKBW93bmVyGAEgASgJUgVvd25lchIhCgx3b3Jrc3BhY2VfaWQYAiABKAlSC3dvcmtzcGFjZUlk');
+final $typed_data.Uint8List currentWorkspaceDescriptor = $convert.base64Decode('ChBDdXJyZW50V29ya3NwYWNlEiEKDHdvcmtzcGFjZV9pZBgBIAEoCVILd29ya3NwYWNlSWQ=');

+ 2 - 0
backend/src/application.rs

@@ -65,6 +65,8 @@ pub fn run(listener: TcpListener, app_ctx: AppContext) -> Result<Server, std::io
 fn ws_scope() -> Scope { web::scope("/ws").service(ws_service::router::start_connection) }
 
 fn user_scope() -> Scope {
+    // https://developer.mozilla.org/en-US/docs/Web/HTTP
+    // TODO: replace GET body with query params
     web::scope("/api")
         // authentication
         .service(web::resource("/auth")

+ 6 - 7
backend/src/workspace_service/app/app.rs

@@ -14,13 +14,10 @@ use flowy_net::errors::invalid_params;
 use flowy_user::entities::parser::UserId;
 use flowy_workspace::{
     entities::{
-        app::{
-            parser::{AppDesc, AppId, AppName},
-            RepeatedApp,
-        },
+        app::parser::{AppDesc, AppId, AppName},
         workspace::parser::WorkspaceId,
     },
-    protobuf::{App, CreateAppParams, QueryAppParams, RepeatedView, UpdateAppParams},
+    protobuf::{App, CreateAppParams, QueryAppParams, RepeatedApp, RepeatedView, UpdateAppParams},
 };
 use protobuf::Message;
 use sqlx::{postgres::PgArguments, PgPool, Postgres, Transaction};
@@ -193,7 +190,7 @@ pub(crate) async fn delete_app(pool: &PgPool, app_id: &str) -> Result<FlowyRespo
 pub(crate) async fn read_apps_belong_to_workspace<'c>(
     transaction: &mut DBTransaction<'_>,
     workspace_id: &str,
-) -> Result<Vec<App>, ServerError> {
+) -> Result<RepeatedApp, ServerError> {
     let workspace_id = WorkspaceId::parse(workspace_id.to_owned()).map_err(invalid_params)?;
     let (sql, args) = SqlBuilder::select("app_table")
         .add_field("*")
@@ -210,5 +207,7 @@ pub(crate) async fn read_apps_belong_to_workspace<'c>(
         .map(|table| make_app_from_table(table, RepeatedView::default()))
         .collect::<Vec<App>>();
 
-    Ok(apps)
+    let mut repeated_app = RepeatedApp::default();
+    repeated_app.set_items(apps.into());
+    Ok(repeated_app)
 }

+ 9 - 4
backend/src/workspace_service/workspace/router.rs

@@ -3,8 +3,7 @@ use crate::{
     workspace_service::workspace::{
         create_workspace,
         delete_workspace,
-        read_workspace,
-        read_workspace_list,
+        read_workspaces,
         update_workspace,
     },
 };
@@ -36,7 +35,13 @@ pub async fn read_handler(
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
     let params: QueryWorkspaceParams = parse_from_payload(payload).await?;
-    let resp = read_workspace(pool.get_ref(), params).await?;
+    let workspace_id = if params.has_workspace_id() {
+        Some(params.get_workspace_id().to_owned())
+    } else {
+        None
+    };
+    let resp = read_workspaces(pool.get_ref(), params.get_user_id(), workspace_id).await?;
+
     Ok(resp.into())
 }
 
@@ -62,6 +67,6 @@ pub async fn workspace_list(
     user_id: Path<String>,
     pool: Data<PgPool>,
 ) -> Result<HttpResponse, ServerError> {
-    let resp = read_workspace_list(pool.get_ref(), &user_id).await?;
+    let resp = read_workspaces(pool.get_ref(), &user_id, None).await?;
     Ok(resp.into())
 }

+ 25 - 53
backend/src/workspace_service/workspace/workspace.rs

@@ -59,44 +59,6 @@ pub(crate) async fn create_workspace(
     FlowyResponse::success().pb(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 (sql, args) = SqlBuilder::select("workspace_table")
-        .add_field("*")
-        .and_where_eq("id", workspace_id)
-        .build()?;
-
-    let table = sqlx::query_as_with::<Postgres, WorkspaceTable, PgArguments>(&sql, args)
-        .fetch_one(&mut transaction)
-        .await
-        .map_err(map_sqlx_error)?;
-
-    let mut repeated_app = RepeatedApp::default();
-    if params.read_apps {
-        repeated_app.set_items(
-            read_apps_belong_to_workspace(&mut transaction, &table.id.to_string())
-                .await?
-                .into(),
-        );
-    }
-
-    transaction
-        .commit()
-        .await
-        .context("Failed to commit SQL transaction to read workspace.")?;
-
-    let workspace = make_workspace_from_table(table, Some(repeated_app));
-    FlowyResponse::success().pb(workspace)
-}
-
 pub(crate) async fn update_workspace(
     pool: &PgPool,
     params: UpdateWorkspaceParams,
@@ -173,38 +135,48 @@ pub(crate) async fn delete_workspace(
     Ok(FlowyResponse::success())
 }
 
-pub async fn read_workspace_list(
+pub async fn read_workspaces(
     pool: &PgPool,
     user_id: &str,
+    workspace_id: Option<String>,
 ) -> Result<FlowyResponse, ServerError> {
+    let user_id = UserId::parse(user_id.to_string()).map_err(invalid_params)?;
     let mut transaction = pool
         .begin()
         .await
-        .context("Failed to acquire a Postgres connection to delete workspace")?;
+        .context("Failed to acquire a Postgres connection to read workspace")?;
 
-    let (sql, args) = SqlBuilder::select("workspace_table")
+    let mut builder = SqlBuilder::select("workspace_table")
         .add_field("*")
-        .and_where_eq("user_id", user_id)
-        .build()?;
+        .and_where_eq("user_id", user_id.as_ref());
+
+    if let Some(workspace_id) = workspace_id {
+        let workspace_id = check_workspace_id(workspace_id)?;
+        builder = builder.and_where_eq("id", workspace_id);
+    }
 
+    let (sql, args) = builder.build()?;
     let tables = sqlx::query_as_with::<Postgres, WorkspaceTable, PgArguments>(&sql, args)
         .fetch_all(&mut transaction)
         .await
         .map_err(map_sqlx_error)?;
 
+    let mut repeated_workspace = RepeatedWorkspace::default();
+    let mut workspaces = vec![];
+    for table in tables {
+        let apps = read_apps_belong_to_workspace(&mut transaction, &table.id.to_string())
+            .await
+            .context("Get workspace app")
+            .unwrap_or(RepeatedApp::default());
+        let workspace = make_workspace_from_table(table, Some(apps));
+        workspaces.push(workspace);
+    }
     transaction
         .commit()
         .await
-        .context("Failed to commit SQL transaction to delete workspace.")?;
+        .context("Failed to commit SQL transaction to read workspace.")?;
 
-    let mut workspace = RepeatedWorkspace::default();
-    workspace.set_items(
-        tables
-            .into_iter()
-            .map(|table| make_workspace_from_table(table, None))
-            .collect::<Vec<Workspace>>()
-            .into(),
-    );
+    repeated_workspace.set_items(workspaces.into());
 
-    FlowyResponse::success().pb(workspace)
+    FlowyResponse::success().pb(repeated_workspace)
 }

+ 3 - 3
backend/tests/api/helper.rs

@@ -33,10 +33,10 @@ impl TestApp {
         workspace
     }
 
-    pub async fn read_workspace(&self, params: QueryWorkspaceParams) -> Option<Workspace> {
+    pub async fn read_workspace(&self, params: QueryWorkspaceParams) -> RepeatedWorkspace {
         let url = format!("{}/api/workspace", self.address);
-        let workspace = read_workspace_request(params, &url).await.unwrap();
-        workspace
+        let workspaces = read_workspaces_request(params, &url).await.unwrap();
+        workspaces
     }
 
     pub async fn update_workspace(&self, params: UpdateWorkspaceParams) {

+ 17 - 20
backend/tests/api/workspace.rs

@@ -21,9 +21,9 @@ async fn workspace_create() {
 #[actix_rt::test]
 async fn workspace_read() {
     let app = spawn_app().await;
-    let (workspace_1, _) = create_test_workspace(&app).await;
-    let read_params = QueryWorkspaceParams::new(&workspace_1.id);
-    log::info!("{:?}", app.read_workspace(read_params).await.unwrap());
+    let (workspace_1, user_id) = create_test_workspace(&app).await;
+    let read_params = QueryWorkspaceParams::new(&user_id).workspace_id(&workspace_1.id);
+    log::info!("{:?}", app.read_workspace(read_params).await);
 }
 
 #[actix_rt::test]
@@ -34,15 +34,18 @@ async fn workspace_read_with_belongs() {
     let _ = create_test_app(&application, &workspace.id, &user_id).await;
     let _ = create_test_app(&application, &workspace.id, &user_id).await;
 
-    let read_params = QueryWorkspaceParams::new(&workspace.id).read_apps();
-    let workspace = application.read_workspace(read_params).await.unwrap();
+    let read_params = QueryWorkspaceParams::new(&user_id)
+        .workspace_id(&workspace.id)
+        .read_apps();
+    let workspaces = application.read_workspace(read_params).await;
+    let workspace = workspaces.items.first().unwrap();
     assert_eq!(workspace.apps.len(), 3);
 }
 
 #[actix_rt::test]
 async fn workspace_update() {
     let app = spawn_app().await;
-    let (workspace_1, _) = create_test_workspace(&app).await;
+    let (workspace_1, user_id) = create_test_workspace(&app).await;
     let update_params = UpdateWorkspaceParams {
         id: workspace_1.id.clone(),
         name: Some("workspace 2".to_string()),
@@ -50,28 +53,23 @@ async fn workspace_update() {
     };
     app.update_workspace(update_params).await;
 
-    let read_params = QueryWorkspaceParams {
-        workspace_id: workspace_1.id.clone(),
-        read_apps: false,
-    };
-    let workspace_2 = app.read_workspace(read_params).await.unwrap();
+    let read_params = QueryWorkspaceParams::new(&user_id).workspace_id(&workspace_1.id);
+    let workspace_2 = app.read_workspace(read_params).await;
     log::info!("{:?}", workspace_2);
 }
 
 #[actix_rt::test]
 async fn workspace_delete() {
     let app = spawn_app().await;
-    let (workspace, _) = create_test_workspace(&app).await;
+    let (workspace, user_id) = create_test_workspace(&app).await;
     let delete_params = DeleteWorkspaceParams {
         workspace_id: workspace.id.clone(),
     };
 
     let _ = app.delete_workspace(delete_params).await;
-    let read_params = QueryWorkspaceParams {
-        workspace_id: workspace.id.clone(),
-        read_apps: false,
-    };
-    assert_eq!(app.read_workspace(read_params).await.is_none(), true);
+    let read_params = QueryWorkspaceParams::new(&user_id).workspace_id(&workspace.id);
+    let repeated_workspace = app.read_workspace(read_params).await;
+    assert_eq!(repeated_workspace.len(), 0);
 }
 
 async fn create_test_workspace(app: &TestApp) -> (Workspace, String) {
@@ -248,8 +246,7 @@ async fn workspace_list_read() {
         let _ = application.create_workspace(params).await;
     }
 
-    let workspaces = application.read_workspace_list(&response.uid).await;
-    // 3 + 1 (created by default)
+    let read_params = QueryWorkspaceParams::new(&response.uid);
+    let workspaces = application.read_workspace(read_params).await;
     assert_eq!(workspaces.len(), 4);
-    log::info!("{:?}", workspaces);
 }

+ 2 - 1
rust-lib/flowy-dispatch/tests/api/helper.rs

@@ -2,7 +2,8 @@ use flowy_dispatch::prelude::*;
 use std::sync::Once;
 
 #[allow(dead_code)]
-= Once::new();
+pub fn setup_env() {
+    static INIT: Once = Once::new();
     INIT.call_once(|| {
         std::env::set_var("RUST_LOG", "flowy_dispatch=debug,debug");
         env_logger::init();

+ 1 - 2
rust-lib/flowy-sdk/src/deps_resolve/workspace_deps_impl.rs

@@ -53,8 +53,7 @@ impl WorkspaceUser for WorkspaceUserImpl {
                 })?;
 
                 Ok(CurrentWorkspace {
-                    owner: user_detail.email,
-                    workspace_id: user_detail.workspace,
+                    workspace_id: "".to_owned(),
                 })
             }),
         }

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

@@ -24,9 +24,6 @@ pub struct UserDetail {
 
     #[pb(index = 4)]
     pub status: UserStatus,
-
-    #[pb(index = 5)]
-    pub workspace: String,
 }
 
 use crate::sql_tables::UserTable;
@@ -37,7 +34,6 @@ impl std::convert::From<UserTable> for UserDetail {
             email: user.email,
             name: user.name,
             status: UserStatus::Login,
-            workspace: user.workspace,
         }
     }
 }

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

@@ -17,9 +17,6 @@ pub struct UpdateUserRequest {
     pub email: Option<String>,
 
     #[pb(index = 4, one_of)]
-    pub workspace: Option<String>,
-
-    #[pb(index = 5, one_of)]
     pub password: Option<String>,
 }
 
@@ -41,11 +38,6 @@ impl UpdateUserRequest {
         self
     }
 
-    pub fn workspace(mut self, workspace: &str) -> Self {
-        self.workspace = Some(workspace.to_owned());
-        self
-    }
-
     pub fn password(mut self, password: &str) -> Self {
         self.password = Some(password.to_owned());
         self
@@ -64,9 +56,6 @@ pub struct UpdateUserParams {
     pub email: Option<String>,
 
     #[pb(index = 4, one_of)]
-    pub workspace: Option<String>,
-
-    #[pb(index = 5, one_of)]
     pub password: Option<String>,
 }
 
@@ -96,19 +85,6 @@ impl TryInto<UpdateUserParams> for UpdateUserRequest {
             ),
         };
 
-        let workspace = match self.workspace {
-            None => None,
-            Some(workspace) => Some(
-                UserWorkspace::parse(workspace)
-                    .map_err(|e| {
-                        ErrorBuilder::new(UserErrCode::UserWorkspaceInvalid)
-                            .msg(e)
-                            .build()
-                    })?
-                    .0,
-            ),
-        };
-
         let password = match self.password {
             None => None,
             Some(password) => Some(
@@ -122,7 +98,6 @@ impl TryInto<UpdateUserParams> for UpdateUserRequest {
             id,
             name,
             email,
-            workspace,
             password,
         })
     }

+ 25 - 71
rust-lib/flowy-user/src/protobuf/model/user_detail.rs

@@ -30,7 +30,6 @@ pub struct UserDetail {
     pub email: ::std::string::String,
     pub name: ::std::string::String,
     pub status: UserStatus,
-    pub workspace: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -139,32 +138,6 @@ impl UserDetail {
     pub fn set_status(&mut self, v: UserStatus) {
         self.status = v;
     }
-
-    // string workspace = 5;
-
-
-    pub fn get_workspace(&self) -> &str {
-        &self.workspace
-    }
-    pub fn clear_workspace(&mut self) {
-        self.workspace.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_workspace(&mut self, v: ::std::string::String) {
-        self.workspace = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_workspace(&mut self) -> &mut ::std::string::String {
-        &mut self.workspace
-    }
-
-    // Take field
-    pub fn take_workspace(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.workspace, ::std::string::String::new())
-    }
 }
 
 impl ::protobuf::Message for UserDetail {
@@ -188,9 +161,6 @@ impl ::protobuf::Message for UserDetail {
                 4 => {
                     ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.status, 4, &mut self.unknown_fields)?
                 },
-                5 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace)?;
-                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -215,9 +185,6 @@ impl ::protobuf::Message for UserDetail {
         if self.status != UserStatus::Unknown {
             my_size += ::protobuf::rt::enum_size(4, self.status);
         }
-        if !self.workspace.is_empty() {
-            my_size += ::protobuf::rt::string_size(5, &self.workspace);
-        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -236,9 +203,6 @@ impl ::protobuf::Message for UserDetail {
         if self.status != UserStatus::Unknown {
             os.write_enum(4, ::protobuf::ProtobufEnum::value(&self.status))?;
         }
-        if !self.workspace.is_empty() {
-            os.write_string(5, &self.workspace)?;
-        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -297,11 +261,6 @@ impl ::protobuf::Message for UserDetail {
                 |m: &UserDetail| { &m.status },
                 |m: &mut UserDetail| { &mut m.status },
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "workspace",
-                |m: &UserDetail| { &m.workspace },
-                |m: &mut UserDetail| { &mut m.workspace },
-            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<UserDetail>(
                 "UserDetail",
                 fields,
@@ -322,7 +281,6 @@ impl ::protobuf::Clear for UserDetail {
         self.email.clear();
         self.name.clear();
         self.status = UserStatus::Unknown;
-        self.workspace.clear();
         self.unknown_fields.clear();
     }
 }
@@ -393,35 +351,31 @@ impl ::protobuf::reflect::ProtobufValue for UserStatus {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x11user_detail.proto\"\x89\x01\n\nUserDetail\x12\x0e\n\x02id\x18\x01\
-    \x20\x01(\tR\x02id\x12\x14\n\x05email\x18\x02\x20\x01(\tR\x05email\x12\
-    \x12\n\x04name\x18\x03\x20\x01(\tR\x04name\x12#\n\x06status\x18\x04\x20\
-    \x01(\x0e2\x0b.UserStatusR\x06status\x12\x1c\n\tworkspace\x18\x05\x20\
-    \x01(\tR\tworkspace*1\n\nUserStatus\x12\x0b\n\x07Unknown\x10\0\x12\t\n\
-    \x05Login\x10\x01\x12\x0b\n\x07Expired\x10\x02J\xd0\x03\n\x06\x12\x04\0\
-    \0\r\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\
-    \x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x12\n\x0b\n\x04\x04\0\x02\0\
-    \x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\
-    \n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\0\x02\0\x03\x12\
-    \x03\x03\x10\x11\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x15\n\x0c\n\
-    \x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\
-    \x12\x03\x04\x0b\x10\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x13\x14\n\
-    \x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x02\
-    \x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x05\x0b\x0f\
-    \n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\n\x04\x04\0\x02\
-    \x03\x12\x03\x06\x04\x1a\n\x0c\n\x05\x04\0\x02\x03\x06\x12\x03\x06\x04\
-    \x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0f\x15\n\x0c\n\x05\x04\0\
-    \x02\x03\x03\x12\x03\x06\x18\x19\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x07\
-    \x04\x19\n\x0c\n\x05\x04\0\x02\x04\x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\
-    \0\x02\x04\x01\x12\x03\x07\x0b\x14\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\
-    \x07\x17\x18\n\n\n\x02\x05\0\x12\x04\t\0\r\x01\n\n\n\x03\x05\0\x01\x12\
-    \x03\t\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\n\x04\x10\n\x0c\n\x05\x05\
-    \0\x02\0\x01\x12\x03\n\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\n\x0e\
-    \x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x0b\x04\x0e\n\x0c\n\x05\x05\0\x02\
-    \x01\x01\x12\x03\x0b\x04\t\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x0b\x0c\
-    \r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x0c\x04\x10\n\x0c\n\x05\x05\0\x02\
-    \x02\x01\x12\x03\x0c\x04\x0b\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x0c\
-    \x0e\x0fb\x06proto3\
+    \n\x11user_detail.proto\"k\n\nUserDetail\x12\x0e\n\x02id\x18\x01\x20\x01\
+    (\tR\x02id\x12\x14\n\x05email\x18\x02\x20\x01(\tR\x05email\x12\x12\n\x04\
+    name\x18\x03\x20\x01(\tR\x04name\x12#\n\x06status\x18\x04\x20\x01(\x0e2\
+    \x0b.UserStatusR\x06status*1\n\nUserStatus\x12\x0b\n\x07Unknown\x10\0\
+    \x12\t\n\x05Login\x10\x01\x12\x0b\n\x07Expired\x10\x02J\x99\x03\n\x06\
+    \x12\x04\0\0\x0c\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\
+    \x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x12\n\x0b\n\x04\
+    \x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\
+    \x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\0\
+    \x02\0\x03\x12\x03\x03\x10\x11\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\
+    \x15\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\
+    \x02\x01\x01\x12\x03\x04\x0b\x10\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\
+    \x04\x13\x14\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\
+    \x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\
+    \x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\
+    \n\x04\x04\0\x02\x03\x12\x03\x06\x04\x1a\n\x0c\n\x05\x04\0\x02\x03\x06\
+    \x12\x03\x06\x04\x0e\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\x0f\x15\n\
+    \x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x06\x18\x19\n\n\n\x02\x05\0\x12\x04\
+    \x08\0\x0c\x01\n\n\n\x03\x05\0\x01\x12\x03\x08\x05\x0f\n\x0b\n\x04\x05\0\
+    \x02\0\x12\x03\t\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\t\x04\x0b\n\
+    \x0c\n\x05\x05\0\x02\0\x02\x12\x03\t\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\
+    \x12\x03\n\x04\x0e\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\n\x04\t\n\x0c\n\
+    \x05\x05\0\x02\x01\x02\x12\x03\n\x0c\r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\
+    \x0b\x04\x10\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x0b\x04\x0b\n\x0c\n\
+    \x05\x05\0\x02\x02\x02\x12\x03\x0b\x0e\x0fb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -5,7 +5,6 @@ message UserDetail {
     string email = 2;
     string name = 3;
     UserStatus status = 4;
-    string workspace = 5;
 }
 enum UserStatus {
     Unknown = 0;

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

@@ -92,13 +92,7 @@ impl UserSession {
         Ok(())
     }
 
-    async fn save_user(&self, mut user: UserTable) -> Result<UserTable, UserError> {
-        if user.workspace.is_empty() {
-            log::info!("Try to create user default workspace");
-            let workspace_id = self.create_default_workspace_if_need(&user.id).await?;
-            user.workspace = workspace_id;
-        }
-
+    async fn save_user(&self, user: UserTable) -> Result<UserTable, UserError> {
         let conn = self.get_db_connection()?;
         let _ = diesel::insert_into(user_table::table)
             .values(user.clone())

+ 3 - 3
rust-lib/flowy-user/src/sql_tables/user.rs

@@ -8,7 +8,7 @@ pub struct UserTable {
     pub(crate) name: String,
     pub(crate) password: String,
     pub(crate) email: String,
-    pub(crate) workspace: String,
+    pub(crate) workspace: String, // deprecated
 }
 
 impl UserTable {
@@ -44,7 +44,7 @@ impl std::convert::From<SignInResponse> for UserTable {
 #[table_name = "user_table"]
 pub struct UserTableChangeset {
     pub id: String,
-    pub workspace: Option<String>,
+    pub workspace: Option<String>, // deprecated
     pub name: Option<String>,
     pub email: Option<String>,
     pub password: Option<String>,
@@ -54,7 +54,7 @@ impl UserTableChangeset {
     pub fn new(params: UpdateUserParams) -> Self {
         UserTableChangeset {
             id: params.id,
-            workspace: params.workspace,
+            workspace: None,
             name: params.name,
             email: params.email,
             password: params.password,

+ 25 - 18
rust-lib/flowy-workspace/src/entities/workspace/workspace_query.rs

@@ -4,32 +4,34 @@ use std::convert::TryInto;
 
 #[derive(Default, ProtoBuf)]
 pub struct QueryWorkspaceRequest {
-    #[pb(index = 1)]
-    pub workspace_id: String,
+    #[pb(index = 1, one_of)]
+    pub workspace_id: Option<String>,
 
     #[pb(index = 2)]
-    pub read_apps: bool,
+    pub user_id: String,
 }
 
+// Read all workspaces if the workspace_id is None
 #[derive(ProtoBuf, Default)]
 pub struct QueryWorkspaceParams {
-    #[pb(index = 1)]
-    pub workspace_id: String,
+    #[pb(index = 1, one_of)]
+    pub workspace_id: Option<String>,
 
     #[pb(index = 2)]
-    pub read_apps: bool,
+    pub user_id: String,
 }
 
 impl QueryWorkspaceParams {
-    pub fn new(workspace_id: &str) -> Self {
+    pub fn new(user_id: &str) -> Self {
         Self {
-            workspace_id: workspace_id.to_owned(),
+            workspace_id: None,
+            user_id: user_id.to_owned(),
             ..Default::default()
         }
     }
 
-    pub fn read_apps(mut self) -> Self {
-        self.read_apps = true;
+    pub fn workspace_id(mut self, workspace_id: &str) -> Self {
+        self.workspace_id = Some(workspace_id.to_string());
         self
     }
 }
@@ -38,17 +40,22 @@ impl TryInto<QueryWorkspaceParams> for QueryWorkspaceRequest {
     type Error = WorkspaceError;
 
     fn try_into(self) -> Result<QueryWorkspaceParams, Self::Error> {
-        let workspace_id = WorkspaceId::parse(self.workspace_id)
-            .map_err(|e| {
-                ErrorBuilder::new(WsErrCode::WorkspaceIdInvalid)
-                    .msg(e)
-                    .build()
-            })?
-            .0;
+        let workspace_id = match self.workspace_id {
+            None => None,
+            Some(workspace_id) => Some(
+                WorkspaceId::parse(workspace_id)
+                    .map_err(|e| {
+                        ErrorBuilder::new(WsErrCode::WorkspaceIdInvalid)
+                            .msg(e)
+                            .build()
+                    })?
+                    .0,
+            ),
+        };
 
         Ok(QueryWorkspaceParams {
             workspace_id,
-            read_apps: self.read_apps,
+            user_id: self.user_id,
         })
     }
 }

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

@@ -3,8 +3,5 @@ use flowy_derive::ProtoBuf;
 #[derive(ProtoBuf, Default, Debug)]
 pub struct CurrentWorkspace {
     #[pb(index = 1)]
-    pub owner: String,
-
-    #[pb(index = 2)]
     pub workspace_id: String,
 }

+ 5 - 1
rust-lib/flowy-workspace/src/event.rs

@@ -21,9 +21,13 @@ pub enum WorkspaceEvent {
     DeleteWorkspace  = 3,
 
     #[display(fmt = "ReadAllWorkspace")]
-    #[event(output = "Workspaces")]
+    #[event(output = "RepeatedWorkspace")]
     ReadAllWorkspace = 4,
 
+    #[display(fmt = "OpenWorkspace")]
+    #[event(input = "QueryWorkspaceRequest", output = "Workspace")]
+    OpenWorkspace    = 5,
+
     #[display(fmt = "CreateApp")]
     #[event(input = "CreateAppRequest", output = "App")]
     CreateApp        = 101,

+ 15 - 7
rust-lib/flowy-workspace/src/handlers/workspace_handler.rs

@@ -29,16 +29,24 @@ pub async fn read_cur_workspace(
 pub async fn read_workspace(
     data: Data<QueryWorkspaceRequest>,
     controller: Unit<Arc<WorkspaceController>>,
-) -> DataResult<Workspace, WorkspaceError> {
+) -> DataResult<RepeatedWorkspace, WorkspaceError> {
     let params: QueryWorkspaceParams = data.into_inner().try_into()?;
-    let mut workspace = controller.read_workspace(&params.workspace_id).await?;
 
-    if params.read_apps {
-        let apps = controller.read_apps(&params.workspace_id).await?;
-        workspace.apps = RepeatedApp { items: apps };
-    }
+    let workspaces = controller.read_workspaces(params.workspace_id).await?;
 
-    data_result(workspace)
+    data_result(workspaces)
+}
+
+#[tracing::instrument(name = "open_workspace", skip(data, controller))]
+pub async fn open_workspace(
+    data: Data<QueryWorkspaceRequest>,
+    controller: Unit<Arc<WorkspaceController>>,
+) -> DataResult<RepeatedWorkspace, WorkspaceError> {
+    let params: QueryWorkspaceParams = data.into_inner().try_into()?;
+
+    let workspaces = controller.open_workspace(params.workspace_id).await?;
+
+    data_result(workspaces)
 }
 
 #[tracing::instrument(name = "get_all_workspaces", skip(controller))]

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

@@ -47,7 +47,8 @@ pub fn create(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>
         .event(WorkspaceEvent::ReadAllWorkspace, read_all_workspaces)
         .event(WorkspaceEvent::CreateWorkspace, create_workspace)
         .event(WorkspaceEvent::ReadCurWorkspace, read_cur_workspace)
-        .event(WorkspaceEvent::ReadWorkspace, read_workspace);
+        .event(WorkspaceEvent::ReadWorkspace, read_workspace)
+        .event(WorkspaceEvent::OpenWorkspace, open_workspace);
 
     module = module
         .event(WorkspaceEvent::CreateApp, create_app)

+ 192 - 95
rust-lib/flowy-workspace/src/protobuf/model/workspace_query.rs

@@ -26,8 +26,9 @@
 #[derive(PartialEq,Clone,Default)]
 pub struct QueryWorkspaceRequest {
     // message fields
-    pub workspace_id: ::std::string::String,
-    pub read_apps: bool,
+    pub user_id: ::std::string::String,
+    // message oneof groups
+    pub one_of_workspace_id: ::std::option::Option<QueryWorkspaceRequest_oneof_one_of_workspace_id>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -39,6 +40,11 @@ impl<'a> ::std::default::Default for &'a QueryWorkspaceRequest {
     }
 }
 
+#[derive(Clone,PartialEq,Debug)]
+pub enum QueryWorkspaceRequest_oneof_one_of_workspace_id {
+    workspace_id(::std::string::String),
+}
+
 impl QueryWorkspaceRequest {
     pub fn new() -> QueryWorkspaceRequest {
         ::std::default::Default::default()
@@ -48,41 +54,75 @@ impl QueryWorkspaceRequest {
 
 
     pub fn get_workspace_id(&self) -> &str {
-        &self.workspace_id
+        match self.one_of_workspace_id {
+            ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(ref v)) => v,
+            _ => "",
+        }
     }
     pub fn clear_workspace_id(&mut self) {
-        self.workspace_id.clear();
+        self.one_of_workspace_id = ::std::option::Option::None;
+    }
+
+    pub fn has_workspace_id(&self) -> bool {
+        match self.one_of_workspace_id {
+            ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(..)) => true,
+            _ => false,
+        }
     }
 
     // Param is passed by value, moved
     pub fn set_workspace_id(&mut self, v: ::std::string::String) {
-        self.workspace_id = v;
+        self.one_of_workspace_id = ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::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
+        if let ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(_)) = self.one_of_workspace_id {
+        } else {
+            self.one_of_workspace_id = ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(::std::string::String::new()));
+        }
+        match self.one_of_workspace_id {
+            ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(ref mut v)) => v,
+            _ => panic!(),
+        }
     }
 
     // Take field
     pub fn take_workspace_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.workspace_id, ::std::string::String::new())
+        if self.has_workspace_id() {
+            match self.one_of_workspace_id.take() {
+                ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
     }
 
-    // bool read_apps = 2;
+    // string user_id = 2;
 
 
-    pub fn get_read_apps(&self) -> bool {
-        self.read_apps
+    pub fn get_user_id(&self) -> &str {
+        &self.user_id
     }
-    pub fn clear_read_apps(&mut self) {
-        self.read_apps = false;
+    pub fn clear_user_id(&mut self) {
+        self.user_id.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_read_apps(&mut self, v: bool) {
-        self.read_apps = v;
+    pub fn set_user_id(&mut self, v: ::std::string::String) {
+        self.user_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_user_id(&mut self) -> &mut ::std::string::String {
+        &mut self.user_id
+    }
+
+    // Take field
+    pub fn take_user_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.user_id, ::std::string::String::new())
     }
 }
 
@@ -96,14 +136,13 @@ impl ::protobuf::Message for QueryWorkspaceRequest {
             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 {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
-                    let tmp = is.read_bool()?;
-                    self.read_apps = tmp;
+                    self.one_of_workspace_id = ::std::option::Option::Some(QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(is.read_string()?));
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.user_id)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -117,11 +156,15 @@ impl ::protobuf::Message for QueryWorkspaceRequest {
     #[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.user_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.user_id);
         }
-        if self.read_apps != false {
-            my_size += 2;
+        if let ::std::option::Option::Some(ref v) = self.one_of_workspace_id {
+            match v {
+                &QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(1, &v);
+                },
+            };
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -129,11 +172,15 @@ impl ::protobuf::Message for QueryWorkspaceRequest {
     }
 
     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.user_id.is_empty() {
+            os.write_string(2, &self.user_id)?;
         }
-        if self.read_apps != false {
-            os.write_bool(2, self.read_apps)?;
+        if let ::std::option::Option::Some(ref v) = self.one_of_workspace_id {
+            match v {
+                &QueryWorkspaceRequest_oneof_one_of_workspace_id::workspace_id(ref v) => {
+                    os.write_string(1, v)?;
+                },
+            };
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -173,15 +220,15 @@ impl ::protobuf::Message for QueryWorkspaceRequest {
         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>(
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
                 "workspace_id",
-                |m: &QueryWorkspaceRequest| { &m.workspace_id },
-                |m: &mut QueryWorkspaceRequest| { &mut m.workspace_id },
+                QueryWorkspaceRequest::has_workspace_id,
+                QueryWorkspaceRequest::get_workspace_id,
             ));
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
-                "read_apps",
-                |m: &QueryWorkspaceRequest| { &m.read_apps },
-                |m: &mut QueryWorkspaceRequest| { &mut m.read_apps },
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "user_id",
+                |m: &QueryWorkspaceRequest| { &m.user_id },
+                |m: &mut QueryWorkspaceRequest| { &mut m.user_id },
             ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<QueryWorkspaceRequest>(
                 "QueryWorkspaceRequest",
@@ -199,8 +246,8 @@ impl ::protobuf::Message for QueryWorkspaceRequest {
 
 impl ::protobuf::Clear for QueryWorkspaceRequest {
     fn clear(&mut self) {
-        self.workspace_id.clear();
-        self.read_apps = false;
+        self.one_of_workspace_id = ::std::option::Option::None;
+        self.user_id.clear();
         self.unknown_fields.clear();
     }
 }
@@ -220,8 +267,9 @@ 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,
+    pub user_id: ::std::string::String,
+    // message oneof groups
+    pub one_of_workspace_id: ::std::option::Option<QueryWorkspaceParams_oneof_one_of_workspace_id>,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -233,6 +281,11 @@ impl<'a> ::std::default::Default for &'a QueryWorkspaceParams {
     }
 }
 
+#[derive(Clone,PartialEq,Debug)]
+pub enum QueryWorkspaceParams_oneof_one_of_workspace_id {
+    workspace_id(::std::string::String),
+}
+
 impl QueryWorkspaceParams {
     pub fn new() -> QueryWorkspaceParams {
         ::std::default::Default::default()
@@ -242,41 +295,75 @@ impl QueryWorkspaceParams {
 
 
     pub fn get_workspace_id(&self) -> &str {
-        &self.workspace_id
+        match self.one_of_workspace_id {
+            ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(ref v)) => v,
+            _ => "",
+        }
     }
     pub fn clear_workspace_id(&mut self) {
-        self.workspace_id.clear();
+        self.one_of_workspace_id = ::std::option::Option::None;
+    }
+
+    pub fn has_workspace_id(&self) -> bool {
+        match self.one_of_workspace_id {
+            ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(..)) => true,
+            _ => false,
+        }
     }
 
     // Param is passed by value, moved
     pub fn set_workspace_id(&mut self, v: ::std::string::String) {
-        self.workspace_id = v;
+        self.one_of_workspace_id = ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::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
+        if let ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(_)) = self.one_of_workspace_id {
+        } else {
+            self.one_of_workspace_id = ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(::std::string::String::new()));
+        }
+        match self.one_of_workspace_id {
+            ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(ref mut v)) => v,
+            _ => panic!(),
+        }
     }
 
     // Take field
     pub fn take_workspace_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.workspace_id, ::std::string::String::new())
+        if self.has_workspace_id() {
+            match self.one_of_workspace_id.take() {
+                ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(v)) => v,
+                _ => panic!(),
+            }
+        } else {
+            ::std::string::String::new()
+        }
     }
 
-    // bool read_apps = 2;
+    // string user_id = 2;
 
 
-    pub fn get_read_apps(&self) -> bool {
-        self.read_apps
+    pub fn get_user_id(&self) -> &str {
+        &self.user_id
     }
-    pub fn clear_read_apps(&mut self) {
-        self.read_apps = false;
+    pub fn clear_user_id(&mut self) {
+        self.user_id.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_read_apps(&mut self, v: bool) {
-        self.read_apps = v;
+    pub fn set_user_id(&mut self, v: ::std::string::String) {
+        self.user_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_user_id(&mut self) -> &mut ::std::string::String {
+        &mut self.user_id
+    }
+
+    // Take field
+    pub fn take_user_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.user_id, ::std::string::String::new())
     }
 }
 
@@ -290,14 +377,13 @@ impl ::protobuf::Message for QueryWorkspaceParams {
             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 {
+                    if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
-                    let tmp = is.read_bool()?;
-                    self.read_apps = tmp;
+                    self.one_of_workspace_id = ::std::option::Option::Some(QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(is.read_string()?));
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.user_id)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -311,11 +397,15 @@ impl ::protobuf::Message for QueryWorkspaceParams {
     #[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.user_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.user_id);
         }
-        if self.read_apps != false {
-            my_size += 2;
+        if let ::std::option::Option::Some(ref v) = self.one_of_workspace_id {
+            match v {
+                &QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(ref v) => {
+                    my_size += ::protobuf::rt::string_size(1, &v);
+                },
+            };
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -323,11 +413,15 @@ impl ::protobuf::Message for QueryWorkspaceParams {
     }
 
     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.user_id.is_empty() {
+            os.write_string(2, &self.user_id)?;
         }
-        if self.read_apps != false {
-            os.write_bool(2, self.read_apps)?;
+        if let ::std::option::Option::Some(ref v) = self.one_of_workspace_id {
+            match v {
+                &QueryWorkspaceParams_oneof_one_of_workspace_id::workspace_id(ref v) => {
+                    os.write_string(1, v)?;
+                },
+            };
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -367,15 +461,15 @@ impl ::protobuf::Message for QueryWorkspaceParams {
         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>(
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
                 "workspace_id",
-                |m: &QueryWorkspaceParams| { &m.workspace_id },
-                |m: &mut QueryWorkspaceParams| { &mut m.workspace_id },
+                QueryWorkspaceParams::has_workspace_id,
+                QueryWorkspaceParams::get_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 },
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "user_id",
+                |m: &QueryWorkspaceParams| { &m.user_id },
+                |m: &mut QueryWorkspaceParams| { &mut m.user_id },
             ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<QueryWorkspaceParams>(
                 "QueryWorkspaceParams",
@@ -393,8 +487,8 @@ impl ::protobuf::Message for QueryWorkspaceParams {
 
 impl ::protobuf::Clear for QueryWorkspaceParams {
     fn clear(&mut self) {
-        self.workspace_id.clear();
-        self.read_apps = false;
+        self.one_of_workspace_id = ::std::option::Option::None;
+        self.user_id.clear();
         self.unknown_fields.clear();
     }
 }
@@ -412,25 +506,28 @@ impl ::protobuf::reflect::ProtobufValue for QueryWorkspaceParams {
 }
 
 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\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\
+    \n\x15workspace_query.proto\"l\n\x15QueryWorkspaceRequest\x12#\n\x0cwork\
+    space_id\x18\x01\x20\x01(\tH\0R\x0bworkspaceId\x12\x17\n\x07user_id\x18\
+    \x02\x20\x01(\tR\x06userIdB\x15\n\x13one_of_workspace_id\"k\n\x14QueryWo\
+    rkspaceParams\x12#\n\x0cworkspace_id\x18\x01\x20\x01(\tH\0R\x0bworkspace\
+    Id\x12\x17\n\x07user_id\x18\x02\x20\x01(\tR\x06userIdB\x15\n\x13one_of_w\
+    orkspace_idJ\xd4\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\x08\0\x12\x03\x03\x04:\n\x0c\n\x05\x04\0\
+    \x08\0\x01\x12\x03\x03\n\x1d\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x208\n\
+    \x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x20&\n\x0c\n\x05\x04\0\x02\0\x01\
+    \x12\x03\x03'3\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x0367\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\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\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\x08\0\x12\
+    \x03\x07\x04:\n\x0c\n\x05\x04\x01\x08\0\x01\x12\x03\x07\n\x1d\n\x0b\n\
+    \x04\x04\x01\x02\0\x12\x03\x07\x208\n\x0c\n\x05\x04\x01\x02\0\x05\x12\
+    \x03\x07\x20&\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07'3\n\x0c\n\x05\
+    \x04\x01\x02\0\x03\x12\x03\x0767\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\n\n\x0c\n\x05\
+    \x04\x01\x02\x01\x01\x12\x03\x08\x0b\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;

+ 10 - 55
rust-lib/flowy-workspace/src/protobuf/model/workspace_user_detail.rs

@@ -26,7 +26,6 @@
 #[derive(PartialEq,Clone,Default)]
 pub struct CurrentWorkspace {
     // message fields
-    pub owner: ::std::string::String,
     pub workspace_id: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
@@ -44,33 +43,7 @@ impl CurrentWorkspace {
         ::std::default::Default::default()
     }
 
-    // string owner = 1;
-
-
-    pub fn get_owner(&self) -> &str {
-        &self.owner
-    }
-    pub fn clear_owner(&mut self) {
-        self.owner.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_owner(&mut self, v: ::std::string::String) {
-        self.owner = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_owner(&mut self) -> &mut ::std::string::String {
-        &mut self.owner
-    }
-
-    // Take field
-    pub fn take_owner(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.owner, ::std::string::String::new())
-    }
-
-    // string workspace_id = 2;
+    // string workspace_id = 1;
 
 
     pub fn get_workspace_id(&self) -> &str {
@@ -107,9 +80,6 @@ impl ::protobuf::Message for CurrentWorkspace {
             let (field_number, wire_type) = is.read_tag_unpack()?;
             match field_number {
                 1 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.owner)?;
-                },
-                2 => {
                     ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.workspace_id)?;
                 },
                 _ => {
@@ -124,11 +94,8 @@ impl ::protobuf::Message for CurrentWorkspace {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if !self.owner.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.owner);
-        }
         if !self.workspace_id.is_empty() {
-            my_size += ::protobuf::rt::string_size(2, &self.workspace_id);
+            my_size += ::protobuf::rt::string_size(1, &self.workspace_id);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -136,11 +103,8 @@ impl ::protobuf::Message for CurrentWorkspace {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.owner.is_empty() {
-            os.write_string(1, &self.owner)?;
-        }
         if !self.workspace_id.is_empty() {
-            os.write_string(2, &self.workspace_id)?;
+            os.write_string(1, &self.workspace_id)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -180,11 +144,6 @@ impl ::protobuf::Message for CurrentWorkspace {
         static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
         descriptor.get(|| {
             let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "owner",
-                |m: &CurrentWorkspace| { &m.owner },
-                |m: &mut CurrentWorkspace| { &mut m.owner },
-            ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "workspace_id",
                 |m: &CurrentWorkspace| { &m.workspace_id },
@@ -206,7 +165,6 @@ impl ::protobuf::Message for CurrentWorkspace {
 
 impl ::protobuf::Clear for CurrentWorkspace {
     fn clear(&mut self) {
-        self.owner.clear();
         self.workspace_id.clear();
         self.unknown_fields.clear();
     }
@@ -225,16 +183,13 @@ impl ::protobuf::reflect::ProtobufValue for CurrentWorkspace {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x1bworkspace_user_detail.proto\"K\n\x10CurrentWorkspace\x12\x14\n\x05\
-    owner\x18\x01\x20\x01(\tR\x05owner\x12!\n\x0cworkspace_id\x18\x02\x20\
-    \x01(\tR\x0bworkspaceIdJ\x98\x01\n\x06\x12\x04\0\0\x05\x01\n\x08\n\x01\
-    \x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\
-    \0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x15\n\
-    \x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\
-    \x12\x03\x03\x0b\x10\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x13\x14\n\
-    \x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x1c\n\x0c\n\x05\x04\0\x02\x01\
-    \x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x17\
-    \n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x1a\x1bb\x06proto3\
+    \n\x1bworkspace_user_detail.proto\"5\n\x10CurrentWorkspace\x12!\n\x0cwor\
+    kspace_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\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\
+    \x03\x04\x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\
+    \x04\0\x02\0\x01\x12\x03\x03\x0b\x17\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\
+    \x03\x1a\x1bb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -1,10 +1,10 @@
 syntax = "proto3";
 
 message QueryWorkspaceRequest {
-    string workspace_id = 1;
-    bool read_apps = 2;
+    oneof one_of_workspace_id { string workspace_id = 1; };
+    string user_id = 2;
 }
 message QueryWorkspaceParams {
-    string workspace_id = 1;
-    bool read_apps = 2;
+    oneof one_of_workspace_id { string workspace_id = 1; };
+    string user_id = 2;
 }

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

@@ -1,6 +1,5 @@
 syntax = "proto3";
 
 message CurrentWorkspace {
-    string owner = 1;
-    string workspace_id = 2;
+    string workspace_id = 1;
 }

+ 48 - 18
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -64,7 +64,20 @@ impl WorkspaceController {
 
     pub async fn read_cur_workspace(&self) -> Result<Workspace, WorkspaceError> {
         let user_workspace = self.user.get_cur_workspace().await?;
-        let workspace = self.read_workspace(&user_workspace.workspace_id).await?;
+        let mut repeated_workspace = self
+            .read_workspaces(Some(user_workspace.workspace_id.clone()))
+            .await?;
+
+        if repeated_workspace.is_empty() {
+            return Err(ErrorBuilder::new(WsErrCode::RecordNotFound).build());
+        }
+
+        debug_assert_eq!(repeated_workspace.len(), 1);
+        let workspace = repeated_workspace
+            .drain(..1)
+            .collect::<Vec<Workspace>>()
+            .pop()
+            .unwrap();
         Ok(workspace)
     }
 
@@ -74,9 +87,31 @@ impl WorkspaceController {
         Ok(apps)
     }
 
-    pub async fn read_workspace(&self, workspace_id: &str) -> Result<Workspace, WorkspaceError> {
-        let workspace_table = self.read_workspace_table(workspace_id).await?;
-        Ok(workspace_table.into())
+    pub async fn open_workspace(&self, workspace_id: &str) -> Result<Workspace, WorkspaceError> {
+        let user_id = self.user.user_id()?;
+        let result = self
+            .read_workspace_table(Some(workspace_id.to_owned()), user_id)
+            .await?
+            .first();
+
+        match result {
+            None => Err(ErrorBuilder::new(WsErrCode::RecordNotFound).build()),
+            Some(workspace_table) => {
+                let workspace: Workspace = workspace_table.into();
+                Ok(workspace)
+            },
+        }
+    }
+
+    pub async fn read_workspaces(
+        &self,
+        workspace_id: Option<String>,
+    ) -> Result<RepeatedWorkspace, WorkspaceError> {
+        let user_id = self.user.user_id()?;
+        let workspace_tables = self.read_workspace_table(workspace_id, user_id).await?;
+        let mut workspaces = vec![];
+
+        Ok(RepeatedWorkspace { items: workspaces })
     }
 
     pub async fn read_workspaces_belong_to_user(&self) -> Result<Vec<Workspace>, WorkspaceError> {
@@ -104,13 +139,14 @@ impl WorkspaceController {
 
     fn read_workspace_table(
         &self,
-        workspace_id: &str,
-    ) -> DispatchFuture<Result<WorkspaceTable, WorkspaceError>> {
+        workspace_id: Option<String>,
+        user_id: String,
+    ) -> DispatchFuture<Result<Vec<WorkspaceTable>, WorkspaceError>> {
         let sql = self.sql.clone();
         let workspace_id = workspace_id.to_owned();
         DispatchFuture {
             fut: Box::pin(async move {
-                let workspace = sql.read_workspace(&workspace_id)?;
+                let workspace = sql.read_workspaces(workspace_id, &user_id)?;
                 // TODO: fetch workspace from remote server
                 Ok(workspace)
             }),
@@ -131,26 +167,20 @@ pub async fn create_workspace_request(
     Ok(workspace)
 }
 
-pub async fn read_workspace_request(
+pub async fn read_workspaces_request(
     params: QueryWorkspaceParams,
     url: &str,
-) -> Result<Option<Workspace>, WorkspaceError> {
+) -> Result<RepeatedWorkspace, WorkspaceError> {
     let result = HttpRequestBuilder::get(&url.to_owned())
         .protobuf(params)?
         .send()
         .await?
-        .response::<Workspace>()
+        .response::<RepeatedWorkspace>()
         .await;
 
     match result {
-        Ok(workspace) => Ok(Some(workspace)),
-        Err(e) => {
-            if e.is_not_found() {
-                Ok(None)
-            } else {
-                Err(e.into())
-            }
-        },
+        Ok(repeated_workspace) => Ok(repeated_workspace),
+        Err(e) => Err(e.into()),
     }
 }
 

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

@@ -24,12 +24,20 @@ impl WorkspaceSql {
         Ok(())
     }
 
-    pub fn read_workspace(&self, workspace_id: &str) -> Result<WorkspaceTable, WorkspaceError> {
-        let workspace = dsl::workspace_table
-            .filter(workspace_table::id.eq(&workspace_id))
-            .first::<WorkspaceTable>(&*(self.database.db_connection()?))?;
+    pub fn read_workspaces(
+        &self,
+        workspace_id: Option<String>,
+        user_id: &str,
+    ) -> Result<Vec<WorkspaceTable>, WorkspaceError> {
+        let mut filter = dsl::workspace_table.filter(workspace_table::user_id.eq(user_id));
 
-        Ok(workspace)
+        if let Some(workspace_id) = workspace_id {
+            filter.filter(workspace_table::id.eq(&workspace_id));
+        }
+
+        let workspaces = filter.load::<WorkspaceTable>(&*(self.database.db_connection()?))?;
+
+        Ok(workspaces)
     }
 
     pub fn update_workspace(