Prechádzať zdrojové kódy

fetch user workspaces

appflowy 3 rokov pred
rodič
commit
0191645d49
30 zmenil súbory, kde vykonal 968 pridanie a 83 odobranie
  1. 1 3
      app_flowy/lib/workspace/application/menu/menu_bloc.dart
  2. 59 0
      app_flowy/lib/workspace/application/menu/menu_user_bloc.dart
  3. 432 0
      app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart
  4. 5 1
      app_flowy/lib/workspace/domain/i_user.dart
  5. 6 1
      app_flowy/lib/workspace/infrastructure/deps_resolver.dart
  6. 10 4
      app_flowy/lib/workspace/infrastructure/i_user_impl.dart
  7. 20 0
      app_flowy/lib/workspace/infrastructure/repos/user_repo.dart
  8. 1 1
      app_flowy/lib/workspace/presentation/widgets/menu/menu_list.dart
  9. 50 11
      app_flowy/lib/workspace/presentation/widgets/menu/menu_user.dart
  10. 14 0
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  11. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  12. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  13. 41 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart
  14. 10 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart
  15. 5 5
      app_flowy/pubspec.lock
  16. 1 0
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  17. 8 0
      rust-lib/flowy-sdk/src/deps_resolve/workspace_deps_impl.rs
  18. 1 1
      rust-lib/flowy-workspace/src/entities/app/app_create.rs
  19. 10 1
      rust-lib/flowy-workspace/src/entities/workspace/workspace_create.rs
  20. 12 8
      rust-lib/flowy-workspace/src/event.rs
  21. 9 0
      rust-lib/flowy-workspace/src/handlers/workspace_handler.rs
  22. 2 0
      rust-lib/flowy-workspace/src/module.rs
  23. 28 22
      rust-lib/flowy-workspace/src/protobuf/model/event.rs
  24. 193 21
      rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs
  25. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/event.proto
  26. 3 0
      rust-lib/flowy-workspace/src/protobuf/proto/workspace_create.proto
  27. 14 1
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  28. 15 0
      rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_sql.rs
  29. 2 1
      rust-lib/flowy-workspace/src/sql_tables/workspace/workspace_table.rs
  30. 11 1
      rust-lib/flowy-workspace/tests/event/workspace_test.rs

+ 1 - 3
app_flowy/lib/workspace/application/menu/menu_bloc.dart

@@ -40,9 +40,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
   }
 
   Stream<MenuState> _performActionOnCreateApp(CreateApp event) async* {
-    await iWorkspaceImpl
-        .createApp(name: event.name, desc: event.desc)
-        .then((result) async* {
+    iWorkspaceImpl.createApp(name: event.name, desc: event.desc).then((result) {
       result.fold(
         (app) => {},
         (error) async* {

+ 59 - 0
app_flowy/lib/workspace/application/menu/menu_user_bloc.dart

@@ -1 +1,60 @@
+import 'package:app_flowy/workspace/domain/i_user.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:dartz/dartz.dart';
 
+part 'menu_user_bloc.freezed.dart';
+
+class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
+  final IUser iUserImpl;
+
+  MenuUserBloc(this.iUserImpl) : super(MenuUserState.initial(iUserImpl.user));
+
+  @override
+  Stream<MenuUserState> mapEventToState(MenuUserEvent event) async* {
+    yield* event.map(
+      initial: (_) async* {
+        // fetch workspaces
+        iUserImpl.fetchWorkspaces().then((result) {
+          result.fold(
+            (workspaces) async* {
+              yield state.copyWith(workspaces: some(workspaces));
+            },
+            (error) async* {
+              yield state.copyWith(successOrFailure: right(error.msg));
+            },
+          );
+        });
+      },
+      fetchWorkspaces: (_FetchWorkspaces value) async* {},
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    super.close();
+  }
+}
+
+@freezed
+class MenuUserEvent with _$MenuUserEvent {
+  const factory MenuUserEvent.initial() = _Initial;
+  const factory MenuUserEvent.fetchWorkspaces() = _FetchWorkspaces;
+}
+
+@freezed
+class MenuUserState with _$MenuUserState {
+  const factory MenuUserState({
+    required UserDetail user,
+    required Option<List<Workspace>> workspaces,
+    required Either<Unit, String> successOrFailure,
+  }) = _MenuUserState;
+
+  factory MenuUserState.initial(UserDetail user) => MenuUserState(
+        user: user,
+        workspaces: none(),
+        successOrFailure: left(unit),
+      );
+}

+ 432 - 0
app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart

@@ -0,0 +1,432 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'menu_user_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$MenuUserEventTearOff {
+  const _$MenuUserEventTearOff();
+
+  _Initial initial() {
+    return const _Initial();
+  }
+
+  _FetchWorkspaces fetchWorkspaces() {
+    return const _FetchWorkspaces();
+  }
+}
+
+/// @nodoc
+const $MenuUserEvent = _$MenuUserEventTearOff();
+
+/// @nodoc
+mixin _$MenuUserEvent {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() fetchWorkspaces,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? fetchWorkspaces,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_FetchWorkspaces value) fetchWorkspaces,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_FetchWorkspaces value)? fetchWorkspaces,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $MenuUserEventCopyWith<$Res> {
+  factory $MenuUserEventCopyWith(
+          MenuUserEvent value, $Res Function(MenuUserEvent) then) =
+      _$MenuUserEventCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$MenuUserEventCopyWithImpl<$Res>
+    implements $MenuUserEventCopyWith<$Res> {
+  _$MenuUserEventCopyWithImpl(this._value, this._then);
+
+  final MenuUserEvent _value;
+  // ignore: unused_field
+  final $Res Function(MenuUserEvent) _then;
+}
+
+/// @nodoc
+abstract class _$InitialCopyWith<$Res> {
+  factory _$InitialCopyWith(_Initial value, $Res Function(_Initial) then) =
+      __$InitialCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$InitialCopyWithImpl<$Res> extends _$MenuUserEventCopyWithImpl<$Res>
+    implements _$InitialCopyWith<$Res> {
+  __$InitialCopyWithImpl(_Initial _value, $Res Function(_Initial) _then)
+      : super(_value, (v) => _then(v as _Initial));
+
+  @override
+  _Initial get _value => super._value as _Initial;
+}
+
+/// @nodoc
+
+class _$_Initial implements _Initial {
+  const _$_Initial();
+
+  @override
+  String toString() {
+    return 'MenuUserEvent.initial()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _Initial);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() fetchWorkspaces,
+  }) {
+    return initial();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_FetchWorkspaces value) fetchWorkspaces,
+  }) {
+    return initial(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_FetchWorkspaces value)? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _Initial implements MenuUserEvent {
+  const factory _Initial() = _$_Initial;
+}
+
+/// @nodoc
+abstract class _$FetchWorkspacesCopyWith<$Res> {
+  factory _$FetchWorkspacesCopyWith(
+          _FetchWorkspaces value, $Res Function(_FetchWorkspaces) then) =
+      __$FetchWorkspacesCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$FetchWorkspacesCopyWithImpl<$Res>
+    extends _$MenuUserEventCopyWithImpl<$Res>
+    implements _$FetchWorkspacesCopyWith<$Res> {
+  __$FetchWorkspacesCopyWithImpl(
+      _FetchWorkspaces _value, $Res Function(_FetchWorkspaces) _then)
+      : super(_value, (v) => _then(v as _FetchWorkspaces));
+
+  @override
+  _FetchWorkspaces get _value => super._value as _FetchWorkspaces;
+}
+
+/// @nodoc
+
+class _$_FetchWorkspaces implements _FetchWorkspaces {
+  const _$_FetchWorkspaces();
+
+  @override
+  String toString() {
+    return 'MenuUserEvent.fetchWorkspaces()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _FetchWorkspaces);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() fetchWorkspaces,
+  }) {
+    return fetchWorkspaces();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (fetchWorkspaces != null) {
+      return fetchWorkspaces();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_FetchWorkspaces value) fetchWorkspaces,
+  }) {
+    return fetchWorkspaces(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_FetchWorkspaces value)? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (fetchWorkspaces != null) {
+      return fetchWorkspaces(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _FetchWorkspaces implements MenuUserEvent {
+  const factory _FetchWorkspaces() = _$_FetchWorkspaces;
+}
+
+/// @nodoc
+class _$MenuUserStateTearOff {
+  const _$MenuUserStateTearOff();
+
+  _MenuUserState call(
+      {required UserDetail user,
+      required Option<List<Workspace>> workspaces,
+      required Either<Unit, String> successOrFailure}) {
+    return _MenuUserState(
+      user: user,
+      workspaces: workspaces,
+      successOrFailure: successOrFailure,
+    );
+  }
+}
+
+/// @nodoc
+const $MenuUserState = _$MenuUserStateTearOff();
+
+/// @nodoc
+mixin _$MenuUserState {
+  UserDetail get user => throw _privateConstructorUsedError;
+  Option<List<Workspace>> get workspaces => throw _privateConstructorUsedError;
+  Either<Unit, String> get successOrFailure =>
+      throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $MenuUserStateCopyWith<MenuUserState> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $MenuUserStateCopyWith<$Res> {
+  factory $MenuUserStateCopyWith(
+          MenuUserState value, $Res Function(MenuUserState) then) =
+      _$MenuUserStateCopyWithImpl<$Res>;
+  $Res call(
+      {UserDetail user,
+      Option<List<Workspace>> workspaces,
+      Either<Unit, String> successOrFailure});
+}
+
+/// @nodoc
+class _$MenuUserStateCopyWithImpl<$Res>
+    implements $MenuUserStateCopyWith<$Res> {
+  _$MenuUserStateCopyWithImpl(this._value, this._then);
+
+  final MenuUserState _value;
+  // ignore: unused_field
+  final $Res Function(MenuUserState) _then;
+
+  @override
+  $Res call({
+    Object? user = freezed,
+    Object? workspaces = freezed,
+    Object? successOrFailure = freezed,
+  }) {
+    return _then(_value.copyWith(
+      user: user == freezed
+          ? _value.user
+          : user // ignore: cast_nullable_to_non_nullable
+              as UserDetail,
+      workspaces: workspaces == freezed
+          ? _value.workspaces
+          : workspaces // ignore: cast_nullable_to_non_nullable
+              as Option<List<Workspace>>,
+      successOrFailure: successOrFailure == freezed
+          ? _value.successOrFailure
+          : successOrFailure // ignore: cast_nullable_to_non_nullable
+              as Either<Unit, String>,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$MenuUserStateCopyWith<$Res>
+    implements $MenuUserStateCopyWith<$Res> {
+  factory _$MenuUserStateCopyWith(
+          _MenuUserState value, $Res Function(_MenuUserState) then) =
+      __$MenuUserStateCopyWithImpl<$Res>;
+  @override
+  $Res call(
+      {UserDetail user,
+      Option<List<Workspace>> workspaces,
+      Either<Unit, String> successOrFailure});
+}
+
+/// @nodoc
+class __$MenuUserStateCopyWithImpl<$Res>
+    extends _$MenuUserStateCopyWithImpl<$Res>
+    implements _$MenuUserStateCopyWith<$Res> {
+  __$MenuUserStateCopyWithImpl(
+      _MenuUserState _value, $Res Function(_MenuUserState) _then)
+      : super(_value, (v) => _then(v as _MenuUserState));
+
+  @override
+  _MenuUserState get _value => super._value as _MenuUserState;
+
+  @override
+  $Res call({
+    Object? user = freezed,
+    Object? workspaces = freezed,
+    Object? successOrFailure = freezed,
+  }) {
+    return _then(_MenuUserState(
+      user: user == freezed
+          ? _value.user
+          : user // ignore: cast_nullable_to_non_nullable
+              as UserDetail,
+      workspaces: workspaces == freezed
+          ? _value.workspaces
+          : workspaces // ignore: cast_nullable_to_non_nullable
+              as Option<List<Workspace>>,
+      successOrFailure: successOrFailure == freezed
+          ? _value.successOrFailure
+          : successOrFailure // ignore: cast_nullable_to_non_nullable
+              as Either<Unit, String>,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_MenuUserState implements _MenuUserState {
+  const _$_MenuUserState(
+      {required this.user,
+      required this.workspaces,
+      required this.successOrFailure});
+
+  @override
+  final UserDetail user;
+  @override
+  final Option<List<Workspace>> workspaces;
+  @override
+  final Either<Unit, String> successOrFailure;
+
+  @override
+  String toString() {
+    return 'MenuUserState(user: $user, workspaces: $workspaces, successOrFailure: $successOrFailure)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _MenuUserState &&
+            (identical(other.user, user) ||
+                const DeepCollectionEquality().equals(other.user, user)) &&
+            (identical(other.workspaces, workspaces) ||
+                const DeepCollectionEquality()
+                    .equals(other.workspaces, workspaces)) &&
+            (identical(other.successOrFailure, successOrFailure) ||
+                const DeepCollectionEquality()
+                    .equals(other.successOrFailure, successOrFailure)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^
+      const DeepCollectionEquality().hash(user) ^
+      const DeepCollectionEquality().hash(workspaces) ^
+      const DeepCollectionEquality().hash(successOrFailure);
+
+  @JsonKey(ignore: true)
+  @override
+  _$MenuUserStateCopyWith<_MenuUserState> get copyWith =>
+      __$MenuUserStateCopyWithImpl<_MenuUserState>(this, _$identity);
+}
+
+abstract class _MenuUserState implements MenuUserState {
+  const factory _MenuUserState(
+      {required UserDetail user,
+      required Option<List<Workspace>> workspaces,
+      required Either<Unit, String> successOrFailure}) = _$_MenuUserState;
+
+  @override
+  UserDetail get user => throw _privateConstructorUsedError;
+  @override
+  Option<List<Workspace>> get workspaces => throw _privateConstructorUsedError;
+  @override
+  Either<Unit, String> get successOrFailure =>
+      throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$MenuUserStateCopyWith<_MenuUserState> get copyWith =>
+      throw _privateConstructorUsedError;
+}

+ 5 - 1
app_flowy/lib/workspace/domain/i_user.dart

@@ -2,13 +2,17 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
 
+export 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
 export 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
 export 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
 export 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 
 abstract class IUser {
-  Future<Either<Unit, WorkspaceError>> deleteWorkspace(String workspaceId);
+  UserDetail get user;
   Future<Either<UserDetail, UserError>> fetchUserDetail(String userId);
+  Future<Either<List<Workspace>, WorkspaceError>> fetchWorkspaces();
+  Future<Either<Unit, WorkspaceError>> deleteWorkspace(String workspaceId);
   Future<Either<Unit, UserError>> signOut();
 }

+ 6 - 1
app_flowy/lib/workspace/infrastructure/deps_resolver.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/workspace/application/app/app_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
+import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_watch.dart';
 import 'package:app_flowy/workspace/application/view/doc_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/view/view_bloc.dart';
@@ -53,12 +54,16 @@ class HomeDepsResolver {
     getIt.registerFactoryParam<IUser, UserDetail, void>(
         (user, _) => IUserImpl(repo: UserRepo(user: user)));
 
-    //Bloc
+    //Menu Bloc
     getIt.registerFactoryParam<MenuBloc, UserDetail, void>(
         (user, _) => MenuBloc(getIt<IWorkspace>(param1: user)));
     getIt.registerFactoryParam<MenuWatchBloc, UserDetail, void>(
         (user, _) => MenuWatchBloc(getIt<IWorkspaceWatch>(param1: user)));
 
+    getIt.registerFactoryParam<MenuUserBloc, UserDetail, void>(
+        (user, _) => MenuUserBloc(getIt<IUser>(param1: user)));
+
+    //
     getIt.registerFactoryParam<AppBloc, String, void>(
         (appId, _) => AppBloc(getIt<IApp>(param1: appId)));
     getIt.registerFactoryParam<AppWatchBloc, String, void>(

+ 10 - 4
app_flowy/lib/workspace/infrastructure/i_user_impl.dart

@@ -13,8 +13,7 @@ class IUserImpl extends IUser {
 
   @override
   Future<Either<Unit, WorkspaceError>> deleteWorkspace(String workspaceId) {
-    // TODO: implement deleteWorkspace
-    throw UnimplementedError();
+    return repo.deleteWorkspace(workspaceId: workspaceId);
   }
 
   @override
@@ -24,7 +23,14 @@ class IUserImpl extends IUser {
 
   @override
   Future<Either<Unit, UserError>> signOut() {
-    // TODO: implement signOut
-    throw UnimplementedError();
+    return repo.signOut();
+  }
+
+  @override
+  UserDetail get user => repo.user;
+
+  @override
+  Future<Either<List<Workspace>, WorkspaceError>> fetchWorkspaces() {
+    return repo.fetchWorkspaces();
   }
 }

+ 20 - 0
app_flowy/lib/workspace/infrastructure/repos/user_repo.dart

@@ -2,6 +2,8 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
 
 class UserRepo {
   final UserDetail user;
@@ -13,4 +15,22 @@ class UserRepo {
       {required String userId}) {
     return UserEventGetStatus().send();
   }
+
+  Future<Either<Unit, WorkspaceError>> deleteWorkspace(
+      {required String workspaceId}) {
+    throw UnimplementedError();
+  }
+
+  Future<Either<Unit, UserError>> signOut() {
+    return UserEventSignOut().send();
+  }
+
+  Future<Either<List<Workspace>, WorkspaceError>> fetchWorkspaces() {
+    return WorkspaceEventReadAllWorkspace().send().then((result) {
+      return result.fold(
+        (workspaces) => left(workspaces.items),
+        (r) => right(r),
+      );
+    });
+  }
 }

+ 1 - 1
app_flowy/lib/workspace/presentation/widgets/menu/menu_list.dart

@@ -31,7 +31,7 @@ class MenuList extends StatelessWidget {
           separatorBuilder: (context, index) => const VSpace(10),
           physics: const BouncingScrollPhysics(),
           itemBuilder: (BuildContext context, int index) {
-            return menuItems[index].build(context);
+            return menuItems[index];
           },
         ),
       ),

+ 50 - 11
app_flowy/lib/workspace/presentation/widgets/menu/menu_user.dart

@@ -1,7 +1,10 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart';
 import 'package:app_flowy/workspace/presentation/widgets/menu/menu_list.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
 class MenuUser extends MenuItem {
   final UserDetail user;
@@ -9,18 +12,54 @@ class MenuUser extends MenuItem {
 
   @override
   Widget build(BuildContext context) {
-    return Row(children: [
-      SizedBox(
-        width: 30,
-        height: 30,
-        child: ClipRRect(
-          borderRadius: BorderRadius.circular(10),
-          child: const Image(image: AssetImage('assets/images/avatar.jpg')),
-        ),
+    return BlocProvider<MenuUserBloc>(
+      create: (context) =>
+          getIt<MenuUserBloc>(param1: user)..add(const MenuUserEvent.initial()),
+      child: BlocBuilder<MenuUserBloc, MenuUserState>(
+        builder: (context, state) => Row(children: [
+          _renderAvatar(context),
+          const HSpace(10),
+          _renderUserName(context),
+          const HSpace(10),
+          _renderDropButton(context),
+        ]),
       ),
-      const HSpace(10),
-      const Text("nathan", style: TextStyle(fontSize: 18)),
-    ]);
+    );
+  }
+
+  Widget _renderAvatar(BuildContext context) {
+    return SizedBox(
+      width: 30,
+      height: 30,
+      child: ClipRRect(
+        borderRadius: BorderRadius.circular(10),
+        child: const Image(image: AssetImage('assets/images/avatar.jpg')),
+      ),
+    );
+  }
+
+  Widget _renderUserName(BuildContext context) {
+    String name = context.read<MenuUserBloc>().state.user.name;
+    if (name.isEmpty) {
+      name = context.read<MenuUserBloc>().state.user.email;
+    }
+    return Flexible(
+      child: Text(
+        name,
+        overflow: TextOverflow.fade,
+        softWrap: false,
+        style: const TextStyle(fontSize: 18),
+      ),
+    );
+  }
+
+  Widget _renderDropButton(BuildContext context) {
+    return IconButton(
+      icon: const Icon(Icons.arrow_drop_down),
+      alignment: Alignment.center,
+      padding: EdgeInsets.zero,
+      onPressed: () {},
+    );
   }
 
   @override

+ 14 - 0
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -118,6 +118,20 @@ class WorkspaceEventGetWorkspace {
     }
 }
 
+class WorkspaceEventReadAllWorkspace {
+    WorkspaceEventReadAllWorkspace();
+
+    Future<Either<Workspaces, WorkspaceError>> send() {
+     final request = FFIRequest.create()
+        ..event = WorkspaceEvent.ReadAllWorkspace.toString();
+
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (okBytes) => left(Workspaces.fromBuffer(okBytes)),
+        (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
+      ));
+    }
+}
+
 class WorkspaceEventCreateApp {
      CreateAppRequest request;
      WorkspaceEventCreateApp(this.request);

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

@@ -13,6 +13,7 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent CreateWorkspace = WorkspaceEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateWorkspace');
   static const WorkspaceEvent GetCurWorkspace = WorkspaceEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetCurWorkspace');
   static const WorkspaceEvent GetWorkspace = WorkspaceEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspace');
+  static const WorkspaceEvent ReadAllWorkspace = WorkspaceEvent._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadAllWorkspace');
   static const WorkspaceEvent CreateApp = WorkspaceEvent._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateApp');
   static const WorkspaceEvent GetApp = WorkspaceEvent._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetApp');
   static const WorkspaceEvent CreateView = WorkspaceEvent._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateView');
@@ -23,6 +24,7 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
     CreateWorkspace,
     GetCurWorkspace,
     GetWorkspace,
+    ReadAllWorkspace,
     CreateApp,
     GetApp,
     CreateView,

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

@@ -15,6 +15,7 @@ const WorkspaceEvent$json = const {
     const {'1': 'CreateWorkspace', '2': 0},
     const {'1': 'GetCurWorkspace', '2': 1},
     const {'1': 'GetWorkspace', '2': 2},
+    const {'1': 'ReadAllWorkspace', '2': 3},
     const {'1': 'CreateApp', '2': 101},
     const {'1': 'GetApp', '2': 102},
     const {'1': 'CreateView', '2': 201},
@@ -24,4 +25,4 @@ const WorkspaceEvent$json = const {
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABITCg9HZXRDdXJXb3Jrc3BhY2UQARIQCgxHZXRXb3Jrc3BhY2UQAhINCglDcmVhdGVBcHAQZRIKCgZHZXRBcHAQZhIPCgpDcmVhdGVWaWV3EMkBEg0KCFJlYWRWaWV3EMoBEg8KClVwZGF0ZVZpZXcQywE=');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABITCg9HZXRDdXJXb3Jrc3BhY2UQARIQCgxHZXRXb3Jrc3BhY2UQAhIUChBSZWFkQWxsV29ya3NwYWNlEAMSDQoJQ3JlYXRlQXBwEGUSCgoGR2V0QXBwEGYSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsB');

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

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

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

@@ -32,3 +32,13 @@ const Workspace$json = const {
 
 /// Descriptor for `Workspace`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIgCgRhcHBzGAQgASgLMgwuUmVwZWF0ZWRBcHBSBGFwcHM=');
+@$core.Deprecated('Use workspacesDescriptor instead')
+const Workspaces$json = const {
+  '1': 'Workspaces',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Workspace', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `Workspaces`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List workspacesDescriptor = $convert.base64Decode('CgpXb3Jrc3BhY2VzEiAKBWl0ZW1zGAEgAygLMgouV29ya3NwYWNlUgVpdGVtcw==');

+ 5 - 5
app_flowy/pubspec.lock

@@ -14,7 +14,7 @@ packages:
       name: analyzer
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.7.1"
+    version: "1.7.2"
   animations:
     dependency: transitive
     description:
@@ -35,7 +35,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   bloc:
     dependency: transitive
     description:
@@ -119,7 +119,7 @@ packages:
       name: charcode
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   checked_yaml:
     dependency: transitive
     description:
@@ -456,7 +456,7 @@ packages:
       name: meta
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.3.0"
+    version: "1.7.0"
   mime:
     dependency: transitive
     description:
@@ -701,7 +701,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   textstyle_extensions:
     dependency: transitive
     description:

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

@@ -33,6 +33,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "UpdateWorkspaceRequest"
         | "CreateWorkspaceRequest"
         | "Workspace"
+        | "Workspaces"
         | "QueryWorkspaceRequest"
         | "CurrentWorkspace"
         | "UpdateViewRequest"

+ 8 - 0
rust-lib/flowy-sdk/src/deps_resolve/workspace_deps_impl.rs

@@ -13,6 +13,14 @@ pub struct WorkspaceUserImpl {
 }
 
 impl WorkspaceUser for WorkspaceUserImpl {
+    fn user_id(&self) -> Result<String, WorkspaceError> {
+        self.user_session.get_user_id().map_err(|e| {
+            ErrorBuilder::new(WsErrCode::UserInternalError)
+                .error(e)
+                .build()
+        })
+    }
+
     fn set_cur_workspace_id(
         &self,
         workspace_id: &str,

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

@@ -84,7 +84,7 @@ pub struct App {
     pub views: RepeatedView,
 }
 
-#[derive(Debug, Default, ProtoBuf)]
+#[derive(PartialEq, Debug, Default, ProtoBuf)]
 pub struct RepeatedApp {
     #[pb(index = 1)]
     pub items: Vec<App>,

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

@@ -1,6 +1,7 @@
 use crate::{
     entities::{app::RepeatedApp, workspace::parser::*},
     errors::*,
+    impl_def_and_def_mut,
 };
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
@@ -36,7 +37,7 @@ impl TryInto<CreateWorkspaceParams> for CreateWorkspaceRequest {
     }
 }
 
-#[derive(ProtoBuf, Default, Debug)]
+#[derive(PartialEq, ProtoBuf, Default, Debug)]
 pub struct Workspace {
     #[pb(index = 1)]
     pub id: String,
@@ -50,3 +51,11 @@ pub struct Workspace {
     #[pb(index = 4)]
     pub apps: RepeatedApp,
 }
+
+#[derive(PartialEq, Debug, Default, ProtoBuf)]
+pub struct Workspaces {
+    #[pb(index = 1)]
+    pub items: Vec<Workspace>,
+}
+
+impl_def_and_def_mut!(Workspaces, Workspace);

+ 12 - 8
rust-lib/flowy-workspace/src/event.rs

@@ -6,33 +6,37 @@ use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
 pub enum WorkspaceEvent {
     #[display(fmt = "CreateWorkspace")]
     #[event(input = "CreateWorkspaceRequest", output = "Workspace")]
-    CreateWorkspace = 0,
+    CreateWorkspace  = 0,
 
     #[display(fmt = "GetCurWorkspace")]
     #[event(output = "Workspace")]
-    GetCurWorkspace = 1,
+    GetCurWorkspace  = 1,
 
     #[display(fmt = "GetWorkspace")]
     #[event(input = "QueryWorkspaceRequest", output = "Workspace")]
-    GetWorkspace    = 2,
+    GetWorkspace     = 2,
+
+    #[display(fmt = "ReadAllWorkspace")]
+    #[event(output = "Workspaces")]
+    ReadAllWorkspace = 3,
 
     #[display(fmt = "CreateApp")]
     #[event(input = "CreateAppRequest", output = "App")]
-    CreateApp       = 101,
+    CreateApp        = 101,
 
     #[display(fmt = "GetApp")]
     #[event(input = "QueryAppRequest", output = "App")]
-    GetApp          = 102,
+    GetApp           = 102,
 
     #[display(fmt = "CreateView")]
     #[event(input = "CreateViewRequest", output = "View")]
-    CreateView      = 201,
+    CreateView       = 201,
 
     #[display(fmt = "ReadView")]
     #[event(input = "QueryViewRequest", output = "View")]
-    ReadView        = 202,
+    ReadView         = 202,
 
     #[display(fmt = "UpdateView")]
     #[event(input = "UpdateViewRequest")]
-    UpdateView      = 203,
+    UpdateView       = 203,
 }

+ 9 - 0
rust-lib/flowy-workspace/src/handlers/workspace_handler.rs

@@ -40,3 +40,12 @@ pub async fn get_workspace(
 
     response_ok(workspace)
 }
+
+#[tracing::instrument(name = "get_all_workspaces", skip(controller))]
+pub async fn read_all_workspaces(
+    controller: Unit<Arc<WorkspaceController>>,
+) -> ResponseResult<Workspaces, WorkspaceError> {
+    let workspaces = controller.read_workspaces_belong_to_user().await?;
+
+    response_ok(Workspaces { items: workspaces })
+}

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

@@ -13,6 +13,7 @@ use std::sync::Arc;
 pub trait WorkspaceDeps: WorkspaceUser + WorkspaceDatabase {}
 
 pub trait WorkspaceUser: Send + Sync {
+    fn user_id(&self) -> Result<String, WorkspaceError>;
     fn set_cur_workspace_id(&self, id: &str) -> DispatchFuture<Result<(), WorkspaceError>>;
     fn get_cur_workspace(&self) -> DispatchFuture<Result<CurrentWorkspace, WorkspaceError>>;
 }
@@ -41,6 +42,7 @@ pub fn create(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>
         .data(workspace_controller)
         .data(app_controller)
         .data(view_controller)
+        .event(WorkspaceEvent::ReadAllWorkspace, read_all_workspaces)
         .event(WorkspaceEvent::CreateWorkspace, create_workspace)
         .event(WorkspaceEvent::GetCurWorkspace, get_cur_workspace)
         .event(WorkspaceEvent::GetWorkspace, get_workspace)

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

@@ -28,6 +28,7 @@ pub enum WorkspaceEvent {
     CreateWorkspace = 0,
     GetCurWorkspace = 1,
     GetWorkspace = 2,
+    ReadAllWorkspace = 3,
     CreateApp = 101,
     GetApp = 102,
     CreateView = 201,
@@ -45,6 +46,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             0 => ::std::option::Option::Some(WorkspaceEvent::CreateWorkspace),
             1 => ::std::option::Option::Some(WorkspaceEvent::GetCurWorkspace),
             2 => ::std::option::Option::Some(WorkspaceEvent::GetWorkspace),
+            3 => ::std::option::Option::Some(WorkspaceEvent::ReadAllWorkspace),
             101 => ::std::option::Option::Some(WorkspaceEvent::CreateApp),
             102 => ::std::option::Option::Some(WorkspaceEvent::GetApp),
             201 => ::std::option::Option::Some(WorkspaceEvent::CreateView),
@@ -59,6 +61,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             WorkspaceEvent::CreateWorkspace,
             WorkspaceEvent::GetCurWorkspace,
             WorkspaceEvent::GetWorkspace,
+            WorkspaceEvent::ReadAllWorkspace,
             WorkspaceEvent::CreateApp,
             WorkspaceEvent::GetApp,
             WorkspaceEvent::CreateView,
@@ -92,29 +95,32 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*\x98\x01\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
+    \n\x0bevent.proto*\xae\x01\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
     ace\x10\0\x12\x13\n\x0fGetCurWorkspace\x10\x01\x12\x10\n\x0cGetWorkspace\
-    \x10\x02\x12\r\n\tCreateApp\x10e\x12\n\n\x06GetApp\x10f\x12\x0f\n\nCreat\
-    eView\x10\xc9\x01\x12\r\n\x08ReadView\x10\xca\x01\x12\x0f\n\nUpdateView\
-    \x10\xcb\x01J\xf2\x02\n\x06\x12\x04\0\0\x0b\x01\n\x08\n\x01\x0c\x12\x03\
-    \0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x0b\x01\n\n\n\x03\x05\0\x01\x12\
-    \x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\
-    \x05\0\x02\0\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\
-    \x03\x16\x17\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x18\n\x0c\n\x05\
-    \x05\0\x02\x01\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x05\0\x02\x01\x02\x12\
-    \x03\x04\x16\x17\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\x15\n\x0c\n\
-    \x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x10\n\x0c\n\x05\x05\0\x02\x02\x02\
-    \x12\x03\x05\x13\x14\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\x04\x14\n\x0c\
-    \n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\r\n\x0c\n\x05\x05\0\x02\x03\x02\
-    \x12\x03\x06\x10\x13\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x07\x04\x11\n\x0c\
-    \n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\n\n\x0c\n\x05\x05\0\x02\x04\x02\
-    \x12\x03\x07\r\x10\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x08\x04\x15\n\x0c\n\
-    \x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x0e\n\x0c\n\x05\x05\0\x02\x05\x02\
-    \x12\x03\x08\x11\x14\n\x0b\n\x04\x05\0\x02\x06\x12\x03\t\x04\x13\n\x0c\n\
-    \x05\x05\0\x02\x06\x01\x12\x03\t\x04\x0c\n\x0c\n\x05\x05\0\x02\x06\x02\
-    \x12\x03\t\x0f\x12\n\x0b\n\x04\x05\0\x02\x07\x12\x03\n\x04\x15\n\x0c\n\
-    \x05\x05\0\x02\x07\x01\x12\x03\n\x04\x0e\n\x0c\n\x05\x05\0\x02\x07\x02\
-    \x12\x03\n\x11\x14b\x06proto3\
+    \x10\x02\x12\x14\n\x10ReadAllWorkspace\x10\x03\x12\r\n\tCreateApp\x10e\
+    \x12\n\n\x06GetApp\x10f\x12\x0f\n\nCreateView\x10\xc9\x01\x12\r\n\x08Rea\
+    dView\x10\xca\x01\x12\x0f\n\nUpdateView\x10\xcb\x01J\x9b\x03\n\x06\x12\
+    \x04\0\0\x0c\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\
+    \x02\0\x0c\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\
+    \x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\
+    \x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\x0b\n\x04\x05\0\
+    \x02\x01\x12\x03\x04\x04\x18\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\
+    \x04\x13\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x16\x17\n\x0b\n\x04\
+    \x05\0\x02\x02\x12\x03\x05\x04\x15\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\
+    \x05\x04\x10\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\x13\x14\n\x0b\n\
+    \x04\x05\0\x02\x03\x12\x03\x06\x04\x19\n\x0c\n\x05\x05\0\x02\x03\x01\x12\
+    \x03\x06\x04\x14\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\x06\x17\x18\n\x0b\
+    \n\x04\x05\0\x02\x04\x12\x03\x07\x04\x14\n\x0c\n\x05\x05\0\x02\x04\x01\
+    \x12\x03\x07\x04\r\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x10\x13\n\
+    \x0b\n\x04\x05\0\x02\x05\x12\x03\x08\x04\x11\n\x0c\n\x05\x05\0\x02\x05\
+    \x01\x12\x03\x08\x04\n\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\r\x10\n\
+    \x0b\n\x04\x05\0\x02\x06\x12\x03\t\x04\x15\n\x0c\n\x05\x05\0\x02\x06\x01\
+    \x12\x03\t\x04\x0e\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x11\x14\n\x0b\
+    \n\x04\x05\0\x02\x07\x12\x03\n\x04\x13\n\x0c\n\x05\x05\0\x02\x07\x01\x12\
+    \x03\n\x04\x0c\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\n\x0f\x12\n\x0b\n\
+    \x04\x05\0\x02\x08\x12\x03\x0b\x04\x15\n\x0c\n\x05\x05\0\x02\x08\x01\x12\
+    \x03\x0b\x04\x0e\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x11\x14b\x06p\
+    roto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 193 - 21
rust-lib/flowy-workspace/src/protobuf/model/workspace_create.rs

@@ -524,33 +524,205 @@ impl ::protobuf::reflect::ProtobufValue for Workspace {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct Workspaces {
+    // message fields
+    pub items: ::protobuf::RepeatedField<Workspace>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a Workspaces {
+    fn default() -> &'a Workspaces {
+        <Workspaces as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl Workspaces {
+    pub fn new() -> Workspaces {
+        ::std::default::Default::default()
+    }
+
+    // repeated .Workspace items = 1;
+
+
+    pub fn get_items(&self) -> &[Workspace] {
+        &self.items
+    }
+    pub fn clear_items(&mut self) {
+        self.items.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_items(&mut self, v: ::protobuf::RepeatedField<Workspace>) {
+        self.items = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_items(&mut self) -> &mut ::protobuf::RepeatedField<Workspace> {
+        &mut self.items
+    }
+
+    // Take field
+    pub fn take_items(&mut self) -> ::protobuf::RepeatedField<Workspace> {
+        ::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new())
+    }
+}
+
+impl ::protobuf::Message for Workspaces {
+    fn is_initialized(&self) -> bool {
+        for v in &self.items {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        for value in &self.items {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.items {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> Workspaces {
+        Workspaces::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Workspace>>(
+                "items",
+                |m: &Workspaces| { &m.items },
+                |m: &mut Workspaces| { &mut m.items },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<Workspaces>(
+                "Workspaces",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static Workspaces {
+        static instance: ::protobuf::rt::LazyV2<Workspaces> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(Workspaces::new)
+    }
+}
+
+impl ::protobuf::Clear for Workspaces {
+    fn clear(&mut self) {
+        self.items.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for Workspaces {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for Workspaces {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 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\x04appsJ\x97\x03\n\x06\x12\x04\0\0\x0c\x01\n\x08\
-    \n\x01\x0c\x12\x03\0\0\x12\n\t\n\x02\x03\0\x12\x03\x01\0\x1a\n\n\n\x02\
-    \x04\0\x12\x04\x03\0\x06\x01\n\n\n\x03\x04\0\x01\x12\x03\x03\x08\x1e\n\
-    \x0b\n\x04\x04\0\x02\0\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\0\x05\
-    \x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x04\x0b\x0f\n\x0c\
-    \n\x05\x04\0\x02\0\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x01\x12\
-    \x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x05\x04\n\n\x0c\n\
-    \x05\x04\0\x02\x01\x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\
-    \x12\x03\x05\x12\x13\n\n\n\x02\x04\x01\x12\x04\x07\0\x0c\x01\n\n\n\x03\
-    \x04\x01\x01\x12\x03\x07\x08\x11\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x08\
-    \x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x08\x04\n\n\x0c\n\x05\x04\
-    \x01\x02\0\x01\x12\x03\x08\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\
-    \x08\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\t\x04\x14\n\x0c\n\x05\
-    \x04\x01\x02\x01\x05\x12\x03\t\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\
-    \x03\t\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\t\x12\x13\n\x0b\n\
-    \x04\x04\x01\x02\x02\x12\x03\n\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\
-    \x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\n\x0b\x0f\n\x0c\
-    \n\x05\x04\x01\x02\x02\x03\x12\x03\n\x12\x13\n\x0b\n\x04\x04\x01\x02\x03\
-    \x12\x03\x0b\x04\x19\n\x0c\n\x05\x04\x01\x02\x03\x06\x12\x03\x0b\x04\x0f\
-    \n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\x0b\x10\x14\n\x0c\n\x05\x04\x01\
-    \x02\x03\x03\x12\x03\x0b\x17\x18b\x06proto3\
+    (\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\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -4,6 +4,7 @@ enum WorkspaceEvent {
     CreateWorkspace = 0;
     GetCurWorkspace = 1;
     GetWorkspace = 2;
+    ReadAllWorkspace = 3;
     CreateApp = 101;
     GetApp = 102;
     CreateView = 201;

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

@@ -11,3 +11,6 @@ message Workspace {
     string desc = 3;
     RepeatedApp apps = 4;
 }
+message Workspaces {
+    repeated Workspace items = 1;
+}

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

@@ -33,7 +33,8 @@ impl WorkspaceController {
         &self,
         params: CreateWorkspaceParams,
     ) -> Result<Workspace, WorkspaceError> {
-        let workspace_table = WorkspaceTable::new(params);
+        let user_id = self.user.user_id()?;
+        let workspace_table = WorkspaceTable::new(params, &user_id);
         let detail: Workspace = workspace_table.clone().into();
         let _ = self.sql.create_workspace(workspace_table)?;
         Ok(detail)
@@ -69,6 +70,18 @@ impl WorkspaceController {
         Ok(workspace_table.into())
     }
 
+    pub async fn read_workspaces_belong_to_user(&self) -> Result<Vec<Workspace>, WorkspaceError> {
+        let user_id = self.user.user_id()?;
+        let workspace = self
+            .sql
+            .read_workspaces_belong_to_user(&user_id)?
+            .into_iter()
+            .map(|workspace_table| workspace_table.into())
+            .collect::<Vec<Workspace>>();
+
+        Ok(workspace)
+    }
+
     pub async fn read_apps(&self, workspace_id: &str) -> Result<Vec<App>, WorkspaceError> {
         let apps = self
             .sql

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

@@ -61,4 +61,19 @@ impl WorkspaceSql {
 
         Ok(apps)
     }
+
+    pub(crate) fn read_workspaces_belong_to_user(
+        &self,
+        user_id: &str,
+    ) -> Result<Vec<WorkspaceTable>, WorkspaceError> {
+        let conn = self.database.db_connection()?;
+        let workspaces = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+            let workspaces = dsl::workspace_table
+                .filter(workspace_table::user_id.eq(user_id))
+                .load::<WorkspaceTable>(&*(conn))?;
+            Ok(workspaces)
+        })?;
+
+        Ok(workspaces)
+    }
 }

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

@@ -19,10 +19,11 @@ pub struct WorkspaceTable {
 
 impl WorkspaceTable {
     #[allow(dead_code)]
-    pub fn new(params: CreateWorkspaceParams) -> Self {
+    pub fn new(params: CreateWorkspaceParams, user_id: &str) -> Self {
         let mut workspace = WorkspaceTable::default();
         workspace.name = params.name;
         workspace.desc = params.desc;
+        workspace.user_id = user_id.to_string();
         workspace
     }
 }

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

@@ -1,6 +1,6 @@
 use crate::helper::*;
 use flowy_workspace::{
-    entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, Workspace},
+    entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, Workspace, Workspaces},
     event::WorkspaceEvent::*,
     prelude::*,
 };
@@ -18,6 +18,16 @@ fn workspace_get_success() {
     dbg!(&workspace);
 }
 
+#[test]
+fn workspace_read_all_success() {
+    let workspaces = SingleUserTestBuilder::new()
+        .event(ReadAllWorkspace)
+        .sync_send()
+        .parse::<Workspaces>();
+
+    dbg!(&workspaces);
+}
+
 #[test]
 fn workspace_create_and_then_get_workspace_success() {
     let workspace = create_workspace(