Pārlūkot izejas kodu

add middleware to handle user unauthorized

appflowy 3 gadi atpakaļ
vecāks
revīzija
47851b3024
57 mainītis faili ar 699 papildinājumiem un 580 dzēšanām
  1. 2 2
      app_flowy/lib/user/infrastructure/i_auth_impl.dart
  2. 2 2
      app_flowy/lib/welcome/application/splash_bloc.dart
  3. 10 2
      app_flowy/lib/welcome/domain/i_splash.dart
  4. 4 4
      app_flowy/lib/welcome/infrastructure/deps_resolver.dart
  5. 5 4
      app_flowy/lib/welcome/infrastructure/i_splash_impl.dart
  6. 4 4
      app_flowy/lib/welcome/presentation/splash_screen.dart
  7. 5 6
      app_flowy/lib/workspace/application/workspace/welcome_bloc.dart
  8. 12 6
      app_flowy/lib/workspace/domain/i_user.dart
  9. 3 4
      app_flowy/lib/workspace/infrastructure/deps_resolver.dart
  10. 102 10
      app_flowy/lib/workspace/infrastructure/i_user_impl.dart
  11. 4 6
      app_flowy/lib/workspace/infrastructure/repos/app_repo.dart
  12. 70 6
      app_flowy/lib/workspace/infrastructure/repos/helper.dart
  13. 0 56
      app_flowy/lib/workspace/infrastructure/repos/user_repo.dart
  14. 2 2
      app_flowy/lib/workspace/infrastructure/repos/view_repo.dart
  15. 2 2
      app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart
  16. 8 8
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-observable/subject.pb.dart
  17. 2 2
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-observable/subject.pbjson.dart
  18. 6 22
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart
  19. 4 12
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart
  20. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/observable.pbenum.dart
  21. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/observable.pbjson.dart
  22. 5 7
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart
  23. 4 5
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart
  24. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbenum.dart
  25. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbjson.dart
  26. 1 1
      backend/Cargo.toml
  27. 2 1
      rust-lib/flowy-net/Cargo.toml
  28. 7 7
      rust-lib/flowy-net/src/errors.rs
  29. 1 0
      rust-lib/flowy-net/src/lib.rs
  30. 54 26
      rust-lib/flowy-net/src/request/request.rs
  31. 1 1
      rust-lib/flowy-net/src/response/mod.rs
  32. 2 2
      rust-lib/flowy-observable/src/entities/subject.rs
  33. 4 4
      rust-lib/flowy-observable/src/lib.rs
  34. 35 35
      rust-lib/flowy-observable/src/protobuf/model/subject.rs
  35. 1 1
      rust-lib/flowy-observable/src/protobuf/proto/subject.proto
  36. 2 2
      rust-lib/flowy-sdk/src/deps_resolve/workspace_deps_impl.rs
  37. 3 3
      rust-lib/flowy-test/src/helper.rs
  38. 1 1
      rust-lib/flowy-user/Cargo.toml
  39. 1 1
      rust-lib/flowy-user/src/entities/parser/user_name.rs
  40. 9 44
      rust-lib/flowy-user/src/errors.rs
  41. 1 1
      rust-lib/flowy-user/src/handlers/user_handler.rs
  42. 3 3
      rust-lib/flowy-user/src/observable/observable.rs
  43. 70 114
      rust-lib/flowy-user/src/protobuf/model/errors.rs
  44. 15 10
      rust-lib/flowy-user/src/protobuf/model/observable.rs
  45. 3 11
      rust-lib/flowy-user/src/protobuf/proto/errors.proto
  46. 2 0
      rust-lib/flowy-user/src/protobuf/proto/observable.proto
  47. 36 9
      rust-lib/flowy-user/src/services/server/server_api.rs
  48. 3 3
      rust-lib/flowy-user/src/services/user/user_session.rs
  49. 1 1
      rust-lib/flowy-workspace/Cargo.toml
  50. 17 14
      rust-lib/flowy-workspace/src/errors.rs
  51. 4 3
      rust-lib/flowy-workspace/src/observable/observable.rs
  52. 57 63
      rust-lib/flowy-workspace/src/protobuf/model/errors.rs
  53. 32 26
      rust-lib/flowy-workspace/src/protobuf/model/observable.rs
  54. 3 4
      rust-lib/flowy-workspace/src/protobuf/proto/errors.proto
  55. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/observable.proto
  56. 53 13
      rust-lib/flowy-workspace/src/services/server/server_api.rs
  57. 10 2
      rust-lib/flowy-workspace/src/services/workspace_controller.rs

+ 2 - 2
app_flowy/lib/user/infrastructure/i_auth_impl.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/presentation/sign_up_screen.dart';
-import 'package:app_flowy/welcome/domain/i_welcome.dart';
+import 'package:app_flowy/welcome/domain/i_splash.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_infra_ui/widget/route/animation.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
@@ -40,7 +40,7 @@ class AuthRouterImpl extends IAuthRouter {
 
   @override
   void pushWelcomeScreen(BuildContext context, UserProfile userProfile) {
-    getIt<IWelcomeRoute>().pushWelcomeScreen(context, userProfile);
+    getIt<ISplashRoute>().pushWelcomeScreen(context, userProfile);
   }
 
   @override

+ 2 - 2
app_flowy/lib/welcome/application/splash_bloc.dart

@@ -1,12 +1,12 @@
 import 'package:app_flowy/welcome/domain/auth_state.dart';
-import 'package:app_flowy/welcome/domain/i_welcome.dart';
+import 'package:app_flowy/welcome/domain/i_splash.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
 part 'splash_bloc.freezed.dart';
 
 class SplashBloc extends Bloc<SplashEvent, SplashState> {
-  final ISplashAuth authImpl;
+  final ISplashUser authImpl;
   SplashBloc(this.authImpl) : super(SplashState.initial());
 
   @override

+ 10 - 2
app_flowy/lib/welcome/domain/i_welcome.dart → app_flowy/lib/welcome/domain/i_splash.dart

@@ -3,11 +3,19 @@ import 'package:flutter/widgets.dart';
 
 import 'auth_state.dart';
 
-abstract class ISplashAuth {
+abstract class ISplashUser {
   Future<AuthState> currentUserProfile();
 }
 
-abstract class IWelcomeRoute {
+abstract class ISplashUserWatch {
+  void startWatching({
+    void Function(AuthState)? authStateCallback,
+  });
+
+  Future<void> stopWatching();
+}
+
+abstract class ISplashRoute {
   void pushSignInScreen(BuildContext context);
   Future<void> pushWelcomeScreen(BuildContext context, UserProfile profile);
   void pushHomeScreen(

+ 4 - 4
app_flowy/lib/welcome/infrastructure/deps_resolver.dart

@@ -1,18 +1,18 @@
 import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
 import 'package:app_flowy/welcome/application/splash_bloc.dart';
-import 'package:app_flowy/welcome/infrastructure/i_welcome_impl.dart';
+import 'package:app_flowy/welcome/infrastructure/i_splash_impl.dart';
 import 'package:app_flowy/workspace/application/home/home_bloc.dart';
 import 'package:app_flowy/workspace/application/home/home_watcher_bloc.dart';
 import 'package:get_it/get_it.dart';
 
 class WelcomeDepsResolver {
   static Future<void> resolve(GetIt getIt) async {
-    getIt.registerFactory<ISplashAuth>(() => WelcomeAuthImpl());
-    getIt.registerFactory<IWelcomeRoute>(() => WelcomeRoute());
+    getIt.registerFactory<ISplashUser>(() => SplashUserImpl());
+    getIt.registerFactory<ISplashRoute>(() => SplashRoute());
     getIt.registerFactory<HomeBloc>(() => HomeBloc());
     getIt.registerFactory<HomeWatcherBloc>(() => HomeWatcherBloc());
     getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
 
-    getIt.registerFactory<SplashBloc>(() => SplashBloc(getIt<ISplashAuth>()));
+    getIt.registerFactory<SplashBloc>(() => SplashBloc(getIt<ISplashUser>()));
   }
 }

+ 5 - 4
app_flowy/lib/welcome/infrastructure/i_welcome_impl.dart → app_flowy/lib/welcome/infrastructure/i_splash_impl.dart

@@ -2,7 +2,7 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/presentation/sign_in_screen.dart';
 import 'package:app_flowy/welcome/domain/auth_state.dart';
-import 'package:app_flowy/welcome/domain/i_welcome.dart';
+import 'package:app_flowy/welcome/domain/i_splash.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
 import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
 import 'package:app_flowy/welcome/presentation/welcome_screen.dart';
@@ -13,9 +13,9 @@ import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 
-export 'package:app_flowy/welcome/domain/i_welcome.dart';
+export 'package:app_flowy/welcome/domain/i_splash.dart';
 
-class WelcomeAuthImpl implements ISplashAuth {
+class SplashUserImpl implements ISplashUser {
   @override
   Future<AuthState> currentUserProfile() {
     final result = UserEventGetUserProfile().send();
@@ -32,7 +32,8 @@ class WelcomeAuthImpl implements ISplashAuth {
   }
 }
 
-class WelcomeRoute implements IWelcomeRoute {
+
+class SplashRoute implements ISplashRoute {
   @override
   Future<void> pushWelcomeScreen(BuildContext context, UserProfile user) async {
     final repo = UserRepo(user: user);

+ 4 - 4
app_flowy/lib/welcome/presentation/splash_screen.dart

@@ -1,4 +1,4 @@
-import 'package:app_flowy/welcome/domain/i_welcome.dart';
+import 'package:app_flowy/welcome/domain/i_splash.dart';
 import 'package:app_flowy/welcome/domain/auth_state.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/welcome/application/splash_bloc.dart';
@@ -37,11 +37,11 @@ class SplashScreen extends StatelessWidget {
     WorkspaceEventReadCurWorkspace().send().then(
       (result) {
         return result.fold(
-          (workspace) => getIt<IWelcomeRoute>()
+          (workspace) => getIt<ISplashRoute>()
               .pushHomeScreen(context, userProfile, workspace.id),
           (error) async {
             assert(error.code == workspace.ErrorCode.CurrentWorkspaceNotFound);
-            getIt<IWelcomeRoute>().pushWelcomeScreen(context, userProfile);
+            getIt<ISplashRoute>().pushWelcomeScreen(context, userProfile);
           },
         );
       },
@@ -50,7 +50,7 @@ class SplashScreen extends StatelessWidget {
 
   void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
     Log.error(result.error);
-    getIt<IWelcomeRoute>().pushSignInScreen(context);
+    getIt<ISplashRoute>().pushSignInScreen(context);
   }
 }
 

+ 5 - 6
app_flowy/lib/workspace/application/workspace/welcome_bloc.dart

@@ -11,7 +11,7 @@ part 'welcome_bloc.freezed.dart';
 
 class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
   UserRepo repo;
-  IUserWorkspaceListWatch watcher;
+  IUserWatch watcher;
   WelcomeBloc({required this.repo, required this.watcher})
       : super(WelcomeState.initial());
 
@@ -20,10 +20,9 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
     WelcomeEvent event,
   ) async* {
     yield* event.map(initial: (e) async* {
-      watcher.startWatching(
-        workspaceListUpdatedCallback: (workspacesOrFail) =>
-            _handleWorkspaceListUpdated(workspacesOrFail),
-      );
+      watcher.setWorkspacesCallback(_workspacesUpdated);
+      watcher.startWatching();
+      //
       yield* _fetchWorkspaces();
     }, openWorkspace: (e) async* {
       yield* _openWorkspace(e.workspace);
@@ -75,7 +74,7 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
     );
   }
 
-  void _handleWorkspaceListUpdated(
+  void _workspacesUpdated(
       Either<List<Workspace>, WorkspaceError> workspacesOrFail) {
     add(WelcomeEvent.workspacesReveived(workspacesOrFail));
   }

+ 12 - 6
app_flowy/lib/workspace/domain/i_user.dart

@@ -8,9 +8,6 @@ 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_profile.pb.dart';
 
-typedef WorkspaceListUpdatedCallback = void Function(
-    Either<List<Workspace>, WorkspaceError> workspacesOrFailed);
-
 abstract class IUser {
   UserProfile get user;
   Future<Either<UserProfile, UserError>> fetchUserProfile(String userId);
@@ -19,9 +16,18 @@ abstract class IUser {
   Future<Either<Unit, UserError>> signOut();
 }
 
-abstract class IUserWorkspaceListWatch {
-  void startWatching(
-      {WorkspaceListUpdatedCallback? workspaceListUpdatedCallback});
+typedef UserProfileUpdateCallback = void Function(
+    Either<UserProfile, UserError>);
+
+typedef AuthChangedCallback = void Function(Either<Unit, UserError>);
+typedef WorkspacesUpdatedCallback = void Function(
+    Either<List<Workspace>, WorkspaceError> workspacesOrFailed);
+
+abstract class IUserWatch {
+  void startWatching();
+  void setProfileCallback(UserProfileUpdateCallback profileCallback);
+  void setAuthCallback(AuthChangedCallback authCallback);
+  void setWorkspacesCallback(WorkspacesUpdatedCallback workspacesCallback);
 
   Future<void> stopWatching();
 }

+ 3 - 4
app_flowy/lib/workspace/infrastructure/deps_resolver.dart

@@ -58,9 +58,8 @@ class HomeDepsResolver {
     // User
     getIt.registerFactoryParam<IUser, UserProfile, void>(
         (user, _) => IUserImpl(repo: UserRepo(user: user)));
-    getIt.registerFactoryParam<IUserWorkspaceListWatch, UserProfile, void>(
-        (user, _) =>
-            IUserWorkspaceListWatchImpl(repo: UserWatchRepo(user: user)));
+    getIt.registerFactoryParam<IUserWatch, UserProfile, void>(
+        (user, _) => IUserWatchImpl(user: user));
 
     //Menu Bloc
     getIt.registerFactoryParam<MenuBloc, UserProfile, String>(
@@ -98,7 +97,7 @@ class HomeDepsResolver {
     getIt.registerFactoryParam<WelcomeBloc, UserProfile, void>(
       (user, _) => WelcomeBloc(
         repo: UserRepo(user: user),
-        watcher: getIt<IUserWorkspaceListWatch>(param1: user),
+        watcher: getIt<IUserWatch>(param1: user),
       ),
     );
 

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

@@ -1,9 +1,18 @@
+import 'dart:typed_data';
+
+import 'package:app_flowy/workspace/infrastructure/repos/helper.dart';
 import 'package:dartz/dartz.dart';
 import 'package:app_flowy/workspace/domain/i_user.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
+import 'package:flowy_sdk/protobuf/flowy-observable/protobuf.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart' as user_error;
+import 'package:flowy_sdk/protobuf/flowy-user/observable.pb.dart' as user;
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
 export 'package:app_flowy/workspace/domain/i_user.dart';
 export 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
+import 'package:flowy_sdk/rust_stream.dart';
+import 'dart:async';
 
 class IUserImpl extends IUser {
   UserRepo repo;
@@ -35,20 +44,103 @@ class IUserImpl extends IUser {
   }
 }
 
-class IUserWorkspaceListWatchImpl extends IUserWorkspaceListWatch {
-  UserWatchRepo repo;
-  IUserWorkspaceListWatchImpl({
-    required this.repo,
-  });
-  @override
-  void startWatching({
-    WorkspaceListUpdatedCallback? workspaceListUpdatedCallback,
+class IUserWatchImpl extends IUserWatch {
+  StreamSubscription<ObservableSubject>? _subscription;
+  WorkspacesUpdatedCallback? _workspacesUpdated;
+  AuthChangedCallback? _authChanged;
+  UserProfileUpdateCallback? _profileUpdated;
+
+  late WorkspaceObservableParser _workspaceParser;
+  late UserObservableParser _userParser;
+  late UserProfile _user;
+  IUserWatchImpl({
+    required UserProfile user,
   }) {
-    repo.startWatching(workspaceListUpdated: workspaceListUpdatedCallback);
+    _user = user;
+  }
+
+  @override
+  void startWatching() {
+    _workspaceParser = WorkspaceObservableParser(
+        id: _user.id, callback: _workspaceObservableCallback);
+
+    _userParser =
+        UserObservableParser(id: _user.id, callback: _userObservableCallback);
+
+    _subscription = RustStreamReceiver.listen((observable) {
+      _workspaceParser.parse(observable);
+      _userParser.parse(observable);
+    });
   }
 
   @override
   Future<void> stopWatching() async {
-    await repo.close();
+    await _subscription?.cancel();
+  }
+
+  @override
+  void setAuthCallback(AuthChangedCallback authCallback) {
+    _authChanged = authCallback;
+  }
+
+  @override
+  void setProfileCallback(UserProfileUpdateCallback profileCallback) {
+    _profileUpdated = profileCallback;
+  }
+
+  @override
+  void setWorkspacesCallback(WorkspacesUpdatedCallback workspacesCallback) {
+    _workspacesUpdated = workspacesCallback;
+  }
+
+  void _workspaceObservableCallback(
+      WorkspaceObservable ty, Either<Uint8List, WorkspaceError> result) {
+    switch (ty) {
+      case WorkspaceObservable.UserCreateWorkspace:
+      case WorkspaceObservable.UserDeleteWorkspace:
+      case WorkspaceObservable.WorkspaceListUpdated:
+        if (_workspacesUpdated != null) {
+          result.fold(
+            (payload) {
+              final workspaces = RepeatedWorkspace.fromBuffer(payload);
+              _workspacesUpdated!(left(workspaces.items));
+            },
+            (error) => _workspacesUpdated!(right(error)),
+          );
+        }
+        break;
+      case WorkspaceObservable.UserUnauthorized:
+        if (_authChanged != null) {
+          result.fold(
+            (_) {},
+            (error) => {
+              _authChanged!(right(UserError.create()
+                ..code = user_error.ErrorCode.UserUnauthorized))
+            },
+          );
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  void _userObservableCallback(
+      user.UserObservable ty, Either<Uint8List, UserError> result) {
+    switch (ty) {
+      case user.UserObservable.UserUnauthorized:
+        if (_profileUpdated != null) {
+          result.fold(
+            (payload) {
+              final userProfile = UserProfile.fromBuffer(payload);
+              _profileUpdated!(left(userProfile));
+            },
+            (error) => _profileUpdated!(right(error)),
+          );
+        }
+        break;
+      default:
+        break;
+    }
   }
 }

+ 4 - 6
app_flowy/lib/workspace/infrastructure/repos/app_repo.dart

@@ -58,7 +58,7 @@ class AppWatchRepository {
   AppCreateViewCallback? _createView;
   AppDeleteViewCallback? _deleteView;
   AppUpdatedCallback? _update;
-  late ObservableExtractor _extractor;
+  late WorkspaceObservableParser _extractor;
   String appId;
 
   AppWatchRepository({
@@ -72,15 +72,13 @@ class AppWatchRepository {
     _createView = createView;
     _deleteView = deleteView;
     _update = update;
-    _extractor = ObservableExtractor(
-      id: appId,
-      callback: (ty, result) => _handleObservableType(ty, result),
-    );
+    _extractor =
+        WorkspaceObservableParser(id: appId, callback: _bservableCallback);
     _subscription =
         RustStreamReceiver.listen((observable) => _extractor.parse(observable));
   }
 
-  void _handleObservableType(
+  void _bservableCallback(
       WorkspaceObservable ty, Either<Uint8List, WorkspaceError> result) {
     switch (ty) {
       case WorkspaceObservable.AppCreateView:

+ 70 - 6
app_flowy/lib/workspace/infrastructure/repos/helper.dart

@@ -1,22 +1,86 @@
 import 'dart:typed_data';
 import 'package:flowy_sdk/protobuf/flowy-observable/protobuf.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
 import 'package:dartz/dartz.dart';
 
-class ObservableExtractor {
+// class WorkspaceObservableParser {
+//   String id;
+//   void Function(WorkspaceObservable, Either<Uint8List, WorkspaceError>)
+//       callback;
+
+//   WorkspaceObservableParser({required this.id, required this.callback});
+//   void parse(ObservableSubject subject) {
+//     if (subject.id != id) {
+//       return;
+//     }
+
+//     final ty = WorkspaceObservable.valueOf(subject.ty);
+//     if (ty == null) {
+//       return;
+//     }
+
+//     if (subject.hasPayload()) {
+//       final bytes = Uint8List.fromList(subject.error);
+//       callback(ty, left(bytes));
+//     } else if (subject.hasError()) {
+//       final bytes = Uint8List.fromList(subject.error);
+//       final error = WorkspaceError.fromBuffer(bytes);
+//       callback(ty, right(error));
+//     } else {
+//       // do nothing
+//     }
+//   }
+// }
+
+typedef UserObservableCallback = void Function(
+    UserObservable, Either<Uint8List, UserError>);
+
+class UserObservableParser extends ObservableParser<UserObservable, UserError> {
+  UserObservableParser(
+      {required String id, required UserObservableCallback callback})
+      : super(
+          id: id,
+          callback: callback,
+          tyParser: (ty) => UserObservable.valueOf(ty),
+          errorParser: (bytes) => UserError.fromBuffer(bytes),
+        );
+}
+
+typedef WorkspaceObservableCallback = void Function(
+    WorkspaceObservable, Either<Uint8List, WorkspaceError>);
+
+class WorkspaceObservableParser
+    extends ObservableParser<WorkspaceObservable, WorkspaceError> {
+  WorkspaceObservableParser(
+      {required String id, required WorkspaceObservableCallback callback})
+      : super(
+          id: id,
+          callback: callback,
+          tyParser: (ty) => WorkspaceObservable.valueOf(ty),
+          errorParser: (bytes) => WorkspaceError.fromBuffer(bytes),
+        );
+}
+
+class ObservableParser<T, E> {
   String id;
-  void Function(WorkspaceObservable, Either<Uint8List, WorkspaceError>)
-      callback;
+  void Function(T, Either<Uint8List, E>) callback;
 
-  ObservableExtractor({required this.id, required this.callback});
+  T? Function(int) tyParser;
+  E Function(Uint8List) errorParser;
 
+  ObservableParser(
+      {required this.id,
+      required this.callback,
+      required this.errorParser,
+      required this.tyParser});
   void parse(ObservableSubject subject) {
     if (subject.id != id) {
       return;
     }
 
-    final ty = WorkspaceObservable.valueOf(subject.ty);
+    final ty = tyParser(subject.ty);
     if (ty == null) {
       return;
     }
@@ -26,7 +90,7 @@ class ObservableExtractor {
       callback(ty, left(bytes));
     } else if (subject.hasError()) {
       final bytes = Uint8List.fromList(subject.error);
-      final error = WorkspaceError.fromBuffer(bytes);
+      final error = errorParser(bytes);
       callback(ty, right(error));
     } else {
       // do nothing

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

@@ -1,16 +1,11 @@
 import 'dart:async';
-import 'dart:typed_data';
-
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/protobuf/flowy-observable/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_query.pb.dart';
-import 'package:flowy_sdk/rust_stream.dart';
 import 'package:app_flowy/workspace/domain/i_user.dart';
 
 class UserRepo {
@@ -67,54 +62,3 @@ class UserRepo {
     });
   }
 }
-
-class UserWatchRepo {
-  StreamSubscription<ObservableSubject>? _subscription;
-  WorkspaceListUpdatedCallback? _workspaceListUpdated;
-  late UserRepo _repo;
-  UserWatchRepo({
-    required UserProfile user,
-  }) {
-    _repo = UserRepo(user: user);
-  }
-
-  void startWatching({WorkspaceListUpdatedCallback? workspaceListUpdated}) {
-    _workspaceListUpdated = workspaceListUpdated;
-    _subscription = RustStreamReceiver.listen((observable) {
-      if (observable.id != _repo.user.id) {
-        return;
-      }
-
-      final ty = WorkspaceObservable.valueOf(observable.ty);
-      if (ty != null) {
-        _handleObservableType(ty, Uint8List.fromList(observable.payload));
-      }
-    });
-  }
-
-  Future<void> close() async {
-    await _subscription?.cancel();
-  }
-
-  void _handleObservableType(WorkspaceObservable ty, Uint8List payload) {
-    if (_workspaceListUpdated == null) {
-      return;
-    }
-
-    switch (ty) {
-      case WorkspaceObservable.UserCreateWorkspace:
-      case WorkspaceObservable.UserDeleteWorkspace:
-      case WorkspaceObservable.WorkspaceListUpdated:
-        if (_workspaceListUpdated == null) {
-          return;
-        }
-
-        final workspaces = RepeatedWorkspace.fromBuffer(payload);
-        _workspaceListUpdated!(left(workspaces.items));
-
-        break;
-      default:
-        break;
-    }
-  }
-}

+ 2 - 2
app_flowy/lib/workspace/infrastructure/repos/view_repo.dart

@@ -30,7 +30,7 @@ class ViewRepository {
 class ViewWatchRepository {
   StreamSubscription<ObservableSubject>? _subscription;
   ViewUpdatedCallback? _update;
-  late ObservableExtractor _extractor;
+  late WorkspaceObservableParser _extractor;
   View view;
 
   ViewWatchRepository({
@@ -41,7 +41,7 @@ class ViewWatchRepository {
     ViewUpdatedCallback? update,
   }) {
     _update = update;
-    _extractor = ObservableExtractor(
+    _extractor = WorkspaceObservableParser(
       id: view.id,
       callback: (ty, result) {
         _handleObservableType(ty, result);

+ 2 - 2
app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart

@@ -67,7 +67,7 @@ class WorkspaceWatchRepo {
   WorkspaceCreateAppCallback? _createApp;
   WorkspaceDeleteAppCallback? _deleteApp;
   WorkspaceUpdatedCallback? _update;
-  late ObservableExtractor _extractor;
+  late WorkspaceObservableParser _extractor;
   final UserProfile user;
   final String workspaceId;
 
@@ -85,7 +85,7 @@ class WorkspaceWatchRepo {
     _deleteApp = deleteApp;
     _update = update;
 
-    _extractor = ObservableExtractor(
+    _extractor = WorkspaceObservableParser(
       id: workspaceId,
       callback: (ty, result) {
         _handleObservableType(ty, result);

+ 8 - 8
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-observable/subject.pb.dart

@@ -31,7 +31,7 @@ class ObservableSubject extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ObservableSubject', createEmptyInstance: create)
     ..oo(0, [4])
     ..oo(1, [5])
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'category')
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'source')
     ..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ty', $pb.PbFieldType.O3)
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..a<$core.List<$core.int>>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'payload', $pb.PbFieldType.OY)
@@ -41,15 +41,15 @@ class ObservableSubject extends $pb.GeneratedMessage {
 
   ObservableSubject._() : super();
   factory ObservableSubject({
-    $core.String? category,
+    $core.String? source,
     $core.int? ty,
     $core.String? id,
     $core.List<$core.int>? payload,
     $core.List<$core.int>? error,
   }) {
     final _result = create();
-    if (category != null) {
-      _result.category = category;
+    if (source != null) {
+      _result.source = source;
     }
     if (ty != null) {
       _result.ty = ty;
@@ -93,13 +93,13 @@ class ObservableSubject extends $pb.GeneratedMessage {
   void clearOneOfError() => clearField($_whichOneof(1));
 
   @$pb.TagNumber(1)
-  $core.String get category => $_getSZ(0);
+  $core.String get source => $_getSZ(0);
   @$pb.TagNumber(1)
-  set category($core.String v) { $_setString(0, v); }
+  set source($core.String v) { $_setString(0, v); }
   @$pb.TagNumber(1)
-  $core.bool hasCategory() => $_has(0);
+  $core.bool hasSource() => $_has(0);
   @$pb.TagNumber(1)
-  void clearCategory() => clearField(1);
+  void clearSource() => clearField(1);
 
   @$pb.TagNumber(2)
   $core.int get ty => $_getIZ(1);

+ 2 - 2
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-observable/subject.pbjson.dart

@@ -12,7 +12,7 @@ import 'dart:typed_data' as $typed_data;
 const ObservableSubject$json = const {
   '1': 'ObservableSubject',
   '2': const [
-    const {'1': 'category', '3': 1, '4': 1, '5': 9, '10': 'category'},
+    const {'1': 'source', '3': 1, '4': 1, '5': 9, '10': 'source'},
     const {'1': 'ty', '3': 2, '4': 1, '5': 5, '10': 'ty'},
     const {'1': 'id', '3': 3, '4': 1, '5': 9, '10': 'id'},
     const {'1': 'payload', '3': 4, '4': 1, '5': 12, '9': 0, '10': 'payload'},
@@ -25,4 +25,4 @@ const ObservableSubject$json = const {
 };
 
 /// Descriptor for `ObservableSubject`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List observableSubjectDescriptor = $convert.base64Decode('ChFPYnNlcnZhYmxlU3ViamVjdBIaCghjYXRlZ29yeRgBIAEoCVIIY2F0ZWdvcnkSDgoCdHkYAiABKAVSAnR5Eg4KAmlkGAMgASgJUgJpZBIaCgdwYXlsb2FkGAQgASgMSABSB3BheWxvYWQSFgoFZXJyb3IYBSABKAxIAVIFZXJyb3JCEAoOb25lX29mX3BheWxvYWRCDgoMb25lX29mX2Vycm9y');
+final $typed_data.Uint8List observableSubjectDescriptor = $convert.base64Decode('ChFPYnNlcnZhYmxlU3ViamVjdBIWCgZzb3VyY2UYASABKAlSBnNvdXJjZRIOCgJ0eRgCIAEoBVICdHkSDgoCaWQYAyABKAlSAmlkEhoKB3BheWxvYWQYBCABKAxIAFIHcGF5bG9hZBIWCgVlcnJvchgFIAEoDEgBUgVlcnJvckIQCg5vbmVfb2ZfcGF5bG9hZEIOCgxvbmVfb2ZfZXJyb3I=');

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

@@ -15,12 +15,6 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode AcquireWriteLockedFailed = ErrorCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AcquireWriteLockedFailed');
   static const ErrorCode AcquireReadLockedFailed = ErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AcquireReadLockedFailed');
   static const ErrorCode UserDatabaseDidNotMatch = ErrorCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseDidNotMatch');
-  static const ErrorCode UserDatabaseInternalError = ErrorCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInternalError');
-  static const ErrorCode SqlInternalError = ErrorCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SqlInternalError');
-  static const ErrorCode DatabaseConnectError = ErrorCode._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseConnectError');
-  static const ErrorCode UserNotLoginYet = ErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
-  static const ErrorCode ReadCurrentIdFailed = ErrorCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadCurrentIdFailed');
-  static const ErrorCode WriteCurrentIdFailed = ErrorCode._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WriteCurrentIdFailed');
   static const ErrorCode EmailIsEmpty = ErrorCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailIsEmpty');
   static const ErrorCode EmailFormatInvalid = ErrorCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailFormatInvalid');
   static const ErrorCode EmailAlreadyExists = ErrorCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailAlreadyExists');
@@ -30,15 +24,13 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode PasswordFormatInvalid = ErrorCode._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordFormatInvalid');
   static const ErrorCode PasswordNotMatch = ErrorCode._(34, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordNotMatch');
   static const ErrorCode UserNameTooLong = ErrorCode._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameTooLong');
-  static const ErrorCode UserNameContainsForbiddenCharacters = ErrorCode._(41, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameContainsForbiddenCharacters');
+  static const ErrorCode ContainForbiddenCharacters = ErrorCode._(41, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ContainForbiddenCharacters');
   static const ErrorCode UserNameIsEmpty = ErrorCode._(42, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameIsEmpty');
   static const ErrorCode UserWorkspaceInvalid = ErrorCode._(50, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserWorkspaceInvalid');
   static const ErrorCode UserIdInvalid = ErrorCode._(51, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
-  static const ErrorCode UserTokenInvalid = ErrorCode._(54, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserTokenInvalid');
+  static const ErrorCode UserUnauthorized = ErrorCode._(54, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
   static const ErrorCode UserNotExist = ErrorCode._(55, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotExist');
-  static const ErrorCode CreateDefaultWorkspaceFailed = ErrorCode._(60, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateDefaultWorkspaceFailed');
-  static const ErrorCode DefaultWorkspaceAlreadyExist = ErrorCode._(61, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DefaultWorkspaceAlreadyExist');
-  static const ErrorCode ServerError = ErrorCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ServerError');
+  static const ErrorCode InternalError = ErrorCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InternalError');
 
   static const $core.List<ErrorCode> values = <ErrorCode> [
     Unknown,
@@ -46,12 +38,6 @@ class ErrorCode extends $pb.ProtobufEnum {
     AcquireWriteLockedFailed,
     AcquireReadLockedFailed,
     UserDatabaseDidNotMatch,
-    UserDatabaseInternalError,
-    SqlInternalError,
-    DatabaseConnectError,
-    UserNotLoginYet,
-    ReadCurrentIdFailed,
-    WriteCurrentIdFailed,
     EmailIsEmpty,
     EmailFormatInvalid,
     EmailAlreadyExists,
@@ -61,15 +47,13 @@ class ErrorCode extends $pb.ProtobufEnum {
     PasswordFormatInvalid,
     PasswordNotMatch,
     UserNameTooLong,
-    UserNameContainsForbiddenCharacters,
+    ContainForbiddenCharacters,
     UserNameIsEmpty,
     UserWorkspaceInvalid,
     UserIdInvalid,
-    UserTokenInvalid,
+    UserUnauthorized,
     UserNotExist,
-    CreateDefaultWorkspaceFailed,
-    DefaultWorkspaceAlreadyExist,
-    ServerError,
+    InternalError,
   ];
 
   static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -17,12 +17,6 @@ const ErrorCode$json = const {
     const {'1': 'AcquireWriteLockedFailed', '2': 2},
     const {'1': 'AcquireReadLockedFailed', '2': 3},
     const {'1': 'UserDatabaseDidNotMatch', '2': 4},
-    const {'1': 'UserDatabaseInternalError', '2': 5},
-    const {'1': 'SqlInternalError', '2': 6},
-    const {'1': 'DatabaseConnectError', '2': 7},
-    const {'1': 'UserNotLoginYet', '2': 10},
-    const {'1': 'ReadCurrentIdFailed', '2': 11},
-    const {'1': 'WriteCurrentIdFailed', '2': 12},
     const {'1': 'EmailIsEmpty', '2': 20},
     const {'1': 'EmailFormatInvalid', '2': 21},
     const {'1': 'EmailAlreadyExists', '2': 22},
@@ -32,20 +26,18 @@ const ErrorCode$json = const {
     const {'1': 'PasswordFormatInvalid', '2': 33},
     const {'1': 'PasswordNotMatch', '2': 34},
     const {'1': 'UserNameTooLong', '2': 40},
-    const {'1': 'UserNameContainsForbiddenCharacters', '2': 41},
+    const {'1': 'ContainForbiddenCharacters', '2': 41},
     const {'1': 'UserNameIsEmpty', '2': 42},
     const {'1': 'UserWorkspaceInvalid', '2': 50},
     const {'1': 'UserIdInvalid', '2': 51},
-    const {'1': 'UserTokenInvalid', '2': 54},
+    const {'1': 'UserUnauthorized', '2': 54},
     const {'1': 'UserNotExist', '2': 55},
-    const {'1': 'CreateDefaultWorkspaceFailed', '2': 60},
-    const {'1': 'DefaultWorkspaceAlreadyExist', '2': 61},
-    const {'1': 'ServerError', '2': 100},
+    const {'1': 'InternalError', '2': 100},
   ],
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhoKFlVzZXJEYXRhYmFzZUluaXRGYWlsZWQQARIcChhBY3F1aXJlV3JpdGVMb2NrZWRGYWlsZWQQAhIbChdBY3F1aXJlUmVhZExvY2tlZEZhaWxlZBADEhsKF1VzZXJEYXRhYmFzZURpZE5vdE1hdGNoEAQSHQoZVXNlckRhdGFiYXNlSW50ZXJuYWxFcnJvchAFEhQKEFNxbEludGVybmFsRXJyb3IQBhIYChREYXRhYmFzZUNvbm5lY3RFcnJvchAHEhMKD1VzZXJOb3RMb2dpbllldBAKEhcKE1JlYWRDdXJyZW50SWRGYWlsZWQQCxIYChRXcml0ZUN1cnJlbnRJZEZhaWxlZBAMEhAKDEVtYWlsSXNFbXB0eRAUEhYKEkVtYWlsRm9ybWF0SW52YWxpZBAVEhYKEkVtYWlsQWxyZWFkeUV4aXN0cxAWEhMKD1Bhc3N3b3JkSXNFbXB0eRAeEhMKD1Bhc3N3b3JkVG9vTG9uZxAfEiQKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzECASGQoVUGFzc3dvcmRGb3JtYXRJbnZhbGlkECESFAoQUGFzc3dvcmROb3RNYXRjaBAiEhMKD1VzZXJOYW1lVG9vTG9uZxAoEicKI1VzZXJOYW1lQ29udGFpbnNGb3JiaWRkZW5DaGFyYWN0ZXJzECkSEwoPVXNlck5hbWVJc0VtcHR5ECoSGAoUVXNlcldvcmtzcGFjZUludmFsaWQQMhIRCg1Vc2VySWRJbnZhbGlkEDMSFAoQVXNlclRva2VuSW52YWxpZBA2EhAKDFVzZXJOb3RFeGlzdBA3EiAKHENyZWF0ZURlZmF1bHRXb3Jrc3BhY2VGYWlsZWQQPBIgChxEZWZhdWx0V29ya3NwYWNlQWxyZWFkeUV4aXN0ED0SDwoLU2VydmVyRXJyb3IQZA==');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhoKFlVzZXJEYXRhYmFzZUluaXRGYWlsZWQQARIcChhBY3F1aXJlV3JpdGVMb2NrZWRGYWlsZWQQAhIbChdBY3F1aXJlUmVhZExvY2tlZEZhaWxlZBADEhsKF1VzZXJEYXRhYmFzZURpZE5vdE1hdGNoEAQSEAoMRW1haWxJc0VtcHR5EBQSFgoSRW1haWxGb3JtYXRJbnZhbGlkEBUSFgoSRW1haWxBbHJlYWR5RXhpc3RzEBYSEwoPUGFzc3dvcmRJc0VtcHR5EB4SEwoPUGFzc3dvcmRUb29Mb25nEB8SJAogUGFzc3dvcmRDb250YWluc0ZvcmJpZENoYXJhY3RlcnMQIBIZChVQYXNzd29yZEZvcm1hdEludmFsaWQQIRIUChBQYXNzd29yZE5vdE1hdGNoECISEwoPVXNlck5hbWVUb29Mb25nECgSHgoaQ29udGFpbkZvcmJpZGRlbkNoYXJhY3RlcnMQKRITCg9Vc2VyTmFtZUlzRW1wdHkQKhIYChRVc2VyV29ya3NwYWNlSW52YWxpZBAyEhEKDVVzZXJJZEludmFsaWQQMxIUChBVc2VyVW5hdXRob3JpemVkEDYSEAoMVXNlck5vdEV4aXN0EDcSEQoNSW50ZXJuYWxFcnJvchBk');
 @$core.Deprecated('Use userErrorDescriptor instead')
 const UserError$json = const {
   '1': 'UserError',

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

@@ -13,11 +13,13 @@ class UserObservable extends $pb.ProtobufEnum {
   static const UserObservable Unknown = UserObservable._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
   static const UserObservable UserAuthChanged = UserObservable._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserAuthChanged');
   static const UserObservable UserProfileUpdated = UserObservable._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserProfileUpdated');
+  static const UserObservable UserUnauthorized = UserObservable._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
 
   static const $core.List<UserObservable> values = <UserObservable> [
     Unknown,
     UserAuthChanged,
     UserProfileUpdated,
+    UserUnauthorized,
   ];
 
   static final $core.Map<$core.int, UserObservable> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -15,8 +15,9 @@ const UserObservable$json = const {
     const {'1': 'Unknown', '2': 0},
     const {'1': 'UserAuthChanged', '2': 1},
     const {'1': 'UserProfileUpdated', '2': 2},
+    const {'1': 'UserUnauthorized', '2': 3},
   ],
 };
 
 /// Descriptor for `UserObservable`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userObservableDescriptor = $convert.base64Decode('Cg5Vc2VyT2JzZXJ2YWJsZRILCgdVbmtub3duEAASEwoPVXNlckF1dGhDaGFuZ2VkEAESFgoSVXNlclByb2ZpbGVVcGRhdGVkEAI=');
+final $typed_data.Uint8List userObservableDescriptor = $convert.base64Decode('Cg5Vc2VyT2JzZXJ2YWJsZRILCgdVbmtub3duEAASEwoPVXNlckF1dGhDaGFuZ2VkEAESFgoSVXNlclByb2ZpbGVVcGRhdGVkEAISFAoQVXNlclVuYXV0aG9yaXplZBAD');

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

@@ -24,10 +24,9 @@ class ErrorCode extends $pb.ProtobufEnum {
   static const ErrorCode ViewDescInvalid = ErrorCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewDescInvalid');
   static const ErrorCode DatabaseConnectionFail = ErrorCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseConnectionFail');
   static const ErrorCode WorkspaceDatabaseError = ErrorCode._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDatabaseError');
-  static const ErrorCode UserInternalError = ErrorCode._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserInternalError');
-  static const ErrorCode UserNotLoginYet = ErrorCode._(103, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
-  static const ErrorCode UserIdIsEmpty = ErrorCode._(104, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdIsEmpty');
-  static const ErrorCode ServerError = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ServerError');
+  static const ErrorCode UserIdIsEmpty = ErrorCode._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdIsEmpty');
+  static const ErrorCode UserUnauthorized = ErrorCode._(103, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
+  static const ErrorCode InternalError = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InternalError');
   static const ErrorCode RecordNotFound = ErrorCode._(1001, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RecordNotFound');
 
   static const $core.List<ErrorCode> values = <ErrorCode> [
@@ -45,10 +44,9 @@ class ErrorCode extends $pb.ProtobufEnum {
     ViewDescInvalid,
     DatabaseConnectionFail,
     WorkspaceDatabaseError,
-    UserInternalError,
-    UserNotLoginYet,
     UserIdIsEmpty,
-    ServerError,
+    UserUnauthorized,
+    InternalError,
     RecordNotFound,
   ];
 

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

@@ -26,16 +26,15 @@ const ErrorCode$json = const {
     const {'1': 'ViewDescInvalid', '2': 23},
     const {'1': 'DatabaseConnectionFail', '2': 100},
     const {'1': 'WorkspaceDatabaseError', '2': 101},
-    const {'1': 'UserInternalError', '2': 102},
-    const {'1': 'UserNotLoginYet', '2': 103},
-    const {'1': 'UserIdIsEmpty', '2': 104},
-    const {'1': 'ServerError', '2': 1000},
+    const {'1': 'UserIdIsEmpty', '2': 102},
+    const {'1': 'UserUnauthorized', '2': 103},
+    const {'1': 'InternalError', '2': 1000},
     const {'1': 'RecordNotFound', '2': 1001},
   ],
 };
 
 /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIYChRXb3Jrc3BhY2VEZXNjSW52YWxpZBAEEhwKGEN1cnJlbnRXb3Jrc3BhY2VOb3RGb3VuZBAFEhAKDEFwcElkSW52YWxpZBAKEhIKDkFwcE5hbWVJbnZhbGlkEAsSEwoPVmlld05hbWVJbnZhbGlkEBQSGAoUVmlld1RodW1ibmFpbEludmFsaWQQFRIRCg1WaWV3SWRJbnZhbGlkEBYSEwoPVmlld0Rlc2NJbnZhbGlkEBcSGgoWRGF0YWJhc2VDb25uZWN0aW9uRmFpbBBkEhoKFldvcmtzcGFjZURhdGFiYXNlRXJyb3IQZRIVChFVc2VySW50ZXJuYWxFcnJvchBmEhMKD1VzZXJOb3RMb2dpbllldBBnEhEKDVVzZXJJZElzRW1wdHkQaBIQCgtTZXJ2ZXJFcnJvchDoBxITCg5SZWNvcmROb3RGb3VuZBDpBw==');
+final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIYChRXb3Jrc3BhY2VEZXNjSW52YWxpZBAEEhwKGEN1cnJlbnRXb3Jrc3BhY2VOb3RGb3VuZBAFEhAKDEFwcElkSW52YWxpZBAKEhIKDkFwcE5hbWVJbnZhbGlkEAsSEwoPVmlld05hbWVJbnZhbGlkEBQSGAoUVmlld1RodW1ibmFpbEludmFsaWQQFRIRCg1WaWV3SWRJbnZhbGlkEBYSEwoPVmlld0Rlc2NJbnZhbGlkEBcSGgoWRGF0YWJhc2VDb25uZWN0aW9uRmFpbBBkEhoKFldvcmtzcGFjZURhdGFiYXNlRXJyb3IQZRIRCg1Vc2VySWRJc0VtcHR5EGYSFAoQVXNlclVuYXV0aG9yaXplZBBnEhIKDUludGVybmFsRXJyb3IQ6AcSEwoOUmVjb3JkTm90Rm91bmQQ6Qc=');
 @$core.Deprecated('Use workspaceErrorDescriptor instead')
 const WorkspaceError$json = const {
   '1': 'WorkspaceError',

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

@@ -21,6 +21,7 @@ class WorkspaceObservable extends $pb.ProtobufEnum {
   static const WorkspaceObservable AppCreateView = WorkspaceObservable._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppCreateView');
   static const WorkspaceObservable AppDeleteView = WorkspaceObservable._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppDeleteView');
   static const WorkspaceObservable ViewUpdated = WorkspaceObservable._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewUpdated');
+  static const WorkspaceObservable UserUnauthorized = WorkspaceObservable._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
 
   static const $core.List<WorkspaceObservable> values = <WorkspaceObservable> [
     Unknown,
@@ -34,6 +35,7 @@ class WorkspaceObservable extends $pb.ProtobufEnum {
     AppCreateView,
     AppDeleteView,
     ViewUpdated,
+    UserUnauthorized,
   ];
 
   static final $core.Map<$core.int, WorkspaceObservable> _byValue = $pb.ProtobufEnum.initByValue(values);

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

@@ -23,8 +23,9 @@ const WorkspaceObservable$json = const {
     const {'1': 'AppCreateView', '2': 23},
     const {'1': 'AppDeleteView', '2': 24},
     const {'1': 'ViewUpdated', '2': 31},
+    const {'1': 'UserUnauthorized', '2': 100},
   ],
 };
 
 /// Descriptor for `WorkspaceObservable`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceObservableDescriptor = $convert.base64Decode('ChNXb3Jrc3BhY2VPYnNlcnZhYmxlEgsKB1Vua25vd24QABIXChNVc2VyQ3JlYXRlV29ya3NwYWNlEAoSFwoTVXNlckRlbGV0ZVdvcmtzcGFjZRALEhQKEFdvcmtzcGFjZVVwZGF0ZWQQDBIWChJXb3Jrc3BhY2VDcmVhdGVBcHAQDRIWChJXb3Jrc3BhY2VEZWxldGVBcHAQDhIYChRXb3Jrc3BhY2VMaXN0VXBkYXRlZBAPEg4KCkFwcFVwZGF0ZWQQFRIRCg1BcHBDcmVhdGVWaWV3EBcSEQoNQXBwRGVsZXRlVmlldxAYEg8KC1ZpZXdVcGRhdGVkEB8=');
+final $typed_data.Uint8List workspaceObservableDescriptor = $convert.base64Decode('ChNXb3Jrc3BhY2VPYnNlcnZhYmxlEgsKB1Vua25vd24QABIXChNVc2VyQ3JlYXRlV29ya3NwYWNlEAoSFwoTVXNlckRlbGV0ZVdvcmtzcGFjZRALEhQKEFdvcmtzcGFjZVVwZGF0ZWQQDBIWChJXb3Jrc3BhY2VDcmVhdGVBcHAQDRIWChJXb3Jrc3BhY2VEZWxldGVBcHAQDhIYChRXb3Jrc3BhY2VMaXN0VXBkYXRlZBAPEg4KCkFwcFVwZGF0ZWQQFRIRCg1BcHBDcmVhdGVWaWV3EBcSEQoNQXBwRGVsZXRlVmlldxAYEg8KC1ZpZXdVcGRhdGVkEB8SFAoQVXNlclVuYXV0aG9yaXplZBBk');

+ 1 - 1
backend/Cargo.toml

@@ -45,7 +45,7 @@ tokio = { version = "1", features = ["full"] }
 flowy-log = { path = "../rust-lib/flowy-log" }
 flowy-user = { path = "../rust-lib/flowy-user" }
 flowy-workspace = { path = "../rust-lib/flowy-workspace" }
-flowy-net = { path = "../rust-lib/flowy-net", features = ["http"] }
+flowy-net = { path = "../rust-lib/flowy-net", features = ["http_server"] }
 
 ormx = { version = "0.7", features = ["postgres"]}
 [dependencies.sqlx]

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

@@ -27,4 +27,5 @@ thiserror = "1.0.24"
 uuid = { version = "0.8", features = ["v4"] }
 
 [features]
-http = ["actix-web"]
+http_server = ["actix-web"]
+flowy_request = []

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

@@ -43,6 +43,8 @@ impl ServerError {
     }
 
     pub fn is_not_found(&self) -> bool { self.code == ErrorCode::RecordNotFound }
+
+    pub fn is_unauthorized(&self) -> bool { self.code == ErrorCode::Unauthorized }
 }
 
 pub fn invalid_params<T: Debug>(e: T) -> ServerError { ServerError::params_invalid().context(e) }
@@ -66,18 +68,16 @@ impl std::convert::From<&ServerError> for FlowyResponse {
 #[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone, derive_more::Display)]
 #[repr(u16)]
 pub enum ErrorCode {
-    #[display(fmt = "Token is invalid")]
-    InvalidToken       = 1,
     #[display(fmt = "Unauthorized")]
-    Unauthorized       = 2,
+    Unauthorized       = 1,
     #[display(fmt = "Payload too large")]
-    PayloadOverflow    = 3,
+    PayloadOverflow    = 2,
     #[display(fmt = "Payload deserialize failed")]
-    PayloadSerdeFail   = 4,
+    PayloadSerdeFail   = 3,
     #[display(fmt = "Unexpected empty payload")]
-    PayloadUnexpectedNone = 5,
+    PayloadUnexpectedNone = 4,
     #[display(fmt = "Params is invalid")]
-    ParamsInvalid      = 6,
+    ParamsInvalid      = 5,
 
     #[display(fmt = "Protobuf serde error")]
     ProtobufError      = 10,

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

@@ -1,4 +1,5 @@
 pub mod config;
 pub mod errors;
+#[cfg(feature = "flowy_request")]
 pub mod request;
 pub mod response;

+ 54 - 26
rust-lib/flowy-net/src/request/request.rs

@@ -1,55 +1,73 @@
-use crate::{errors::ServerError, response::FlowyResponse};
+use crate::{
+    errors::{ErrorCode, ServerError},
+    response::FlowyResponse,
+};
 use bytes::Bytes;
 use hyper::http;
 use protobuf::ProtobufError;
 use reqwest::{header::HeaderMap, Client, Method, Response};
 use std::{
     convert::{TryFrom, TryInto},
+    sync::Arc,
     time::Duration,
 };
 use tokio::sync::oneshot;
 
+pub trait ResponseMiddleware {
+    fn receive_response(&self, response: &FlowyResponse);
+}
+
 pub struct HttpRequestBuilder {
     url: String,
     body: Option<Bytes>,
     response: Option<Bytes>,
     headers: HeaderMap,
     method: Method,
+    middleware: Vec<Arc<dyn ResponseMiddleware + Send + Sync>>,
 }
 
 impl HttpRequestBuilder {
-    fn new(url: &str) -> Self {
+    pub fn new() -> Self {
         Self {
-            url: url.to_owned(),
+            url: "".to_owned(),
             body: None,
             response: None,
             headers: HeaderMap::new(),
             method: Method::GET,
+            middleware: Vec::new(),
         }
     }
 
-    pub fn get(url: &str) -> Self {
-        let mut builder = Self::new(url);
-        builder.method = Method::GET;
-        builder
+    pub fn middleware<T>(mut self, middleware: Arc<T>) -> Self
+    where
+        T: 'static + ResponseMiddleware + Send + Sync,
+    {
+        self.middleware.push(middleware);
+        self
+    }
+
+    pub fn get(mut self, url: &str) -> Self {
+        self.url = url.to_owned();
+        self.method = Method::GET;
+        self
     }
 
-    pub fn post(url: &str) -> Self {
-        let mut builder = Self::new(url);
-        builder.method = Method::POST;
-        builder
+    pub fn post(mut self, url: &str) -> Self {
+        self.url = url.to_owned();
+        self.method = Method::POST;
+        self
     }
 
-    pub fn patch(url: &str) -> Self {
-        let mut builder = Self::new(url);
-        builder.method = Method::PATCH;
-        builder
+    pub fn patch(mut self, url: &str) -> Self {
+        self.url = url.to_owned();
+        self.method = Method::PATCH;
+        self
     }
 
-    pub fn delete(url: &str) -> Self {
-        let mut builder = Self::new(url);
-        builder.method = Method::DELETE;
-        builder
+    pub fn delete(mut self, url: &str) -> Self {
+        self.url = url.to_owned();
+        self.method = Method::DELETE;
+        self
     }
 
     pub fn header(mut self, key: &'static str, value: &str) -> Self {
@@ -57,9 +75,9 @@ impl HttpRequestBuilder {
         self
     }
 
-    pub fn protobuf<T1>(self, body: T1) -> Result<Self, ServerError>
+    pub fn protobuf<T>(self, body: T) -> Result<Self, ServerError>
     where
-        T1: TryInto<Bytes, Error = ProtobufError>,
+        T: TryInto<Bytes, Error = ProtobufError>,
     {
         let body: Bytes = body.try_into()?;
         self.bytes(body)
@@ -95,13 +113,16 @@ impl HttpRequestBuilder {
         });
 
         let response = rx.await??;
-
-        match get_response_data(response).await {
-            Ok(bytes) => {
-                self.response = Some(bytes);
+        let flowy_response = flowy_response_from(response).await?;
+        self.middleware.iter().for_each(|middleware| {
+            middleware.receive_response(&flowy_response);
+        });
+        match flowy_response.error {
+            None => {
+                self.response = Some(flowy_response.data);
                 Ok(self)
             },
-            Err(error) => Err(error),
+            Some(error) => Err(error),
         }
     }
 
@@ -119,6 +140,13 @@ impl HttpRequestBuilder {
     }
 }
 
+async fn flowy_response_from(original: Response) -> Result<FlowyResponse, ServerError> {
+    let bytes = original.bytes().await?;
+    let response: FlowyResponse = serde_json::from_slice(&bytes)?;
+    Ok(response)
+}
+
+#[allow(dead_code)]
 async fn get_response_data(original: Response) -> Result<Bytes, ServerError> {
     if original.status() == http::StatusCode::OK {
         let bytes = original.bytes().await?;

+ 1 - 1
rust-lib/flowy-net/src/response/mod.rs

@@ -1,7 +1,7 @@
 mod response;
 mod response_serde;
 
-#[cfg(feature = "http")]
+#[cfg(feature = "http_server")]
 mod response_http;
 
 pub use response::*;

+ 2 - 2
rust-lib/flowy-observable/src/entities/subject.rs

@@ -3,7 +3,7 @@ use flowy_derive::ProtoBuf;
 #[derive(Debug, Clone, ProtoBuf)]
 pub struct ObservableSubject {
     #[pb(index = 1)]
-    pub category: String,
+    pub source: String,
 
     #[pb(index = 2)]
     pub ty: i32,
@@ -21,7 +21,7 @@ pub struct ObservableSubject {
 impl std::default::Default for ObservableSubject {
     fn default() -> Self {
         Self {
-            category: "".to_string(),
+            source: "".to_string(),
             ty: 0,
             id: "".to_string(),
             payload: None,

+ 4 - 4
rust-lib/flowy-observable/src/lib.rs

@@ -11,18 +11,18 @@ pub struct ObservableBuilder {
     id: String,
     payload: Option<Bytes>,
     error: Option<Bytes>,
+    source: String,
     ty: i32,
-    category: String,
 }
 
 impl ObservableBuilder {
-    pub fn new<T: Into<i32>>(id: &str, ty: T, category: &str) -> Self {
+    pub fn new<T: Into<i32>>(id: &str, ty: T, source: &str) -> Self {
         Self {
             id: id.to_owned(),
             ty: ty.into(),
             payload: None,
             error: None,
-            category: category.to_owned(),
+            source: source.to_owned(),
         }
     }
 
@@ -65,7 +65,7 @@ impl ObservableBuilder {
         };
 
         let subject = ObservableSubject {
-            category: self.category,
+            source: self.source,
             ty: self.ty,
             id: self.id,
             payload,

+ 35 - 35
rust-lib/flowy-observable/src/protobuf/model/subject.rs

@@ -26,7 +26,7 @@
 #[derive(PartialEq,Clone,Default)]
 pub struct ObservableSubject {
     // message fields
-    pub category: ::std::string::String,
+    pub source: ::std::string::String,
     pub ty: i32,
     pub id: ::std::string::String,
     // message oneof groups
@@ -58,30 +58,30 @@ impl ObservableSubject {
         ::std::default::Default::default()
     }
 
-    // string category = 1;
+    // string source = 1;
 
 
-    pub fn get_category(&self) -> &str {
-        &self.category
+    pub fn get_source(&self) -> &str {
+        &self.source
     }
-    pub fn clear_category(&mut self) {
-        self.category.clear();
+    pub fn clear_source(&mut self) {
+        self.source.clear();
     }
 
     // Param is passed by value, moved
-    pub fn set_category(&mut self, v: ::std::string::String) {
-        self.category = v;
+    pub fn set_source(&mut self, v: ::std::string::String) {
+        self.source = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_category(&mut self) -> &mut ::std::string::String {
-        &mut self.category
+    pub fn mut_source(&mut self) -> &mut ::std::string::String {
+        &mut self.source
     }
 
     // Take field
-    pub fn take_category(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.category, ::std::string::String::new())
+    pub fn take_source(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.source, ::std::string::String::new())
     }
 
     // int32 ty = 2;
@@ -234,7 +234,7 @@ impl ::protobuf::Message for ObservableSubject {
             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.category)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.source)?;
                 },
                 2 => {
                     if wire_type != ::protobuf::wire_format::WireTypeVarint {
@@ -270,8 +270,8 @@ impl ::protobuf::Message for ObservableSubject {
     #[allow(unused_variables)]
     fn compute_size(&self) -> u32 {
         let mut my_size = 0;
-        if !self.category.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.category);
+        if !self.source.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.source);
         }
         if self.ty != 0 {
             my_size += ::protobuf::rt::value_size(2, self.ty, ::protobuf::wire_format::WireTypeVarint);
@@ -299,8 +299,8 @@ impl ::protobuf::Message for ObservableSubject {
     }
 
     fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.category.is_empty() {
-            os.write_string(1, &self.category)?;
+        if !self.source.is_empty() {
+            os.write_string(1, &self.source)?;
         }
         if self.ty != 0 {
             os.write_int32(2, self.ty)?;
@@ -361,9 +361,9 @@ impl ::protobuf::Message for ObservableSubject {
         descriptor.get(|| {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "category",
-                |m: &ObservableSubject| { &m.category },
-                |m: &mut ObservableSubject| { &mut m.category },
+                "source",
+                |m: &ObservableSubject| { &m.source },
+                |m: &mut ObservableSubject| { &mut m.source },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt32>(
                 "ty",
@@ -401,7 +401,7 @@ impl ::protobuf::Message for ObservableSubject {
 
 impl ::protobuf::Clear for ObservableSubject {
     fn clear(&mut self) {
-        self.category.clear();
+        self.source.clear();
         self.ty = 0;
         self.id.clear();
         self.one_of_payload = ::std::option::Option::None;
@@ -423,20 +423,20 @@ impl ::protobuf::reflect::ProtobufValue for ObservableSubject {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\rsubject.proto\"\xa5\x01\n\x11ObservableSubject\x12\x1a\n\x08category\
-    \x18\x01\x20\x01(\tR\x08category\x12\x0e\n\x02ty\x18\x02\x20\x01(\x05R\
-    \x02ty\x12\x0e\n\x02id\x18\x03\x20\x01(\tR\x02id\x12\x1a\n\x07payload\
-    \x18\x04\x20\x01(\x0cH\0R\x07payload\x12\x16\n\x05error\x18\x05\x20\x01(\
-    \x0cH\x01R\x05errorB\x10\n\x0eone_of_payloadB\x0e\n\x0cone_of_errorJ\xf3\
-    \x02\n\x06\x12\x04\0\0\x08\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
-    \x04\0\x12\x04\x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x19\n\
-    \x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x18\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\x13\n\x0c\
-    \n\x05\x04\0\x02\0\x03\x12\x03\x03\x16\x17\n\x0b\n\x04\x04\0\x02\x01\x12\
-    \x03\x04\x04\x11\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\t\n\x0c\n\
-    \x05\x04\0\x02\x01\x01\x12\x03\x04\n\x0c\n\x0c\n\x05\x04\0\x02\x01\x03\
-    \x12\x03\x04\x0f\x10\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x12\n\x0c\
-    \n\x05\x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\
+    \n\rsubject.proto\"\xa1\x01\n\x11ObservableSubject\x12\x16\n\x06source\
+    \x18\x01\x20\x01(\tR\x06source\x12\x0e\n\x02ty\x18\x02\x20\x01(\x05R\x02\
+    ty\x12\x0e\n\x02id\x18\x03\x20\x01(\tR\x02id\x12\x1a\n\x07payload\x18\
+    \x04\x20\x01(\x0cH\0R\x07payload\x12\x16\n\x05error\x18\x05\x20\x01(\x0c\
+    H\x01R\x05errorB\x10\n\x0eone_of_payloadB\x0e\n\x0cone_of_errorJ\xf3\x02\
+    \n\x06\x12\x04\0\0\x08\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\
+    \0\x12\x04\x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x19\n\x0b\n\
+    \x04\x04\0\x02\0\x12\x03\x03\x04\x16\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\x11\n\x0c\n\x05\
+    \x04\0\x02\0\x03\x12\x03\x03\x14\x15\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
+    \x04\x04\x11\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\t\n\x0c\n\x05\
+    \x04\0\x02\x01\x01\x12\x03\x04\n\x0c\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
+    \x03\x04\x0f\x10\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x12\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\r\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x10\x11\n\
     \x0b\n\x04\x04\0\x08\0\x12\x03\x06\x04/\n\x0c\n\x05\x04\0\x08\0\x01\x12\
     \x03\x06\n\x18\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x1b-\n\x0c\n\x05\

+ 1 - 1
rust-lib/flowy-observable/src/protobuf/proto/subject.proto

@@ -1,7 +1,7 @@
 syntax = "proto3";
 
 message ObservableSubject {
-    string category = 1;
+    string source = 1;
     int32 ty = 2;
     string id = 3;
     oneof one_of_payload { bytes payload = 4; };

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

@@ -15,13 +15,13 @@ impl WorkspaceUser for WorkspaceUserImpl {
     fn user_id(&self) -> Result<String, WorkspaceError> {
         self.user_session
             .user_id()
-            .map_err(|e| ErrorBuilder::new(ErrorCode::UserInternalError).error(e).build())
+            .map_err(|e| ErrorBuilder::new(ErrorCode::InternalError).error(e).build())
     }
 
     fn token(&self) -> Result<String, WorkspaceError> {
         self.user_session
             .token()
-            .map_err(|e| ErrorBuilder::new(ErrorCode::UserInternalError).error(e).build())
+            .map_err(|e| ErrorBuilder::new(ErrorCode::InternalError).error(e).build())
     }
 }
 

+ 3 - 3
rust-lib/flowy-test/src/helper.rs

@@ -43,7 +43,7 @@ const DEFAULT_WORKSPACE: &'static str = "Default_Workspace";
 pub(crate) fn create_default_workspace_if_need(dispatch: Arc<EventDispatch>, user_id: &str) -> Result<(), UserError> {
     let key = format!("{}{}", user_id, DEFAULT_WORKSPACE);
     if KV::get_bool(&key).unwrap_or(false) {
-        return Err(ErrorBuilder::new(ErrorCode::DefaultWorkspaceAlreadyExist).build());
+        return Err(ErrorBuilder::new(ErrorCode::InternalError).build());
     }
     KV::set_bool(&key, true);
 
@@ -57,9 +57,9 @@ pub(crate) fn create_default_workspace_if_need(dispatch: Arc<EventDispatch>, use
     let request = ModuleRequest::new(CreateWorkspace).payload(payload);
     let result = EventDispatch::sync_send(dispatch.clone(), request)
         .parse::<Workspace, WorkspaceError>()
-        .map_err(|e| ErrorBuilder::new(ErrorCode::CreateDefaultWorkspaceFailed).error(e).build())?;
+        .map_err(|e| ErrorBuilder::new(ErrorCode::InternalError).error(e).build())?;
 
-    let workspace = result.map_err(|e| ErrorBuilder::new(ErrorCode::CreateDefaultWorkspaceFailed).error(e).build())?;
+    let workspace = result.map_err(|e| ErrorBuilder::new(ErrorCode::InternalError).error(e).build())?;
     let query: Bytes = QueryWorkspaceRequest {
         workspace_id: Some(workspace.id.clone()),
     }

+ 1 - 1
rust-lib/flowy-user/Cargo.toml

@@ -13,7 +13,7 @@ flowy-derive = { path = "../flowy-derive" }
 flowy-database = { path = "../flowy-database" }
 flowy-sqlite = { path = "../flowy-sqlite" }
 flowy-infra = { path = "../flowy-infra" }
-flowy-net = { path = "../flowy-net" }
+flowy-net = { path = "../flowy-net", features = ["flowy_request"] }
 flowy-observable = { path = "../flowy-observable" }
 
 tracing = { version = "0.1", features = ["log"] }

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

@@ -26,7 +26,7 @@ impl UserName {
         let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g));
 
         if contains_forbidden_characters {
-            return Err(ErrorCode::UserNameContainsForbiddenCharacters);
+            return Err(ErrorCode::ContainForbiddenCharacters);
         }
 
         Ok(Self(s))

+ 9 - 44
rust-lib/flowy-user/src/errors.rs

@@ -33,21 +33,6 @@ pub enum ErrorCode {
     AcquireReadLockedFailed = 3,
     #[display(fmt = "Opening database is not belonging to the current user")]
     UserDatabaseDidNotMatch = 4,
-    #[display(fmt = "Database internal error")]
-    UserDatabaseInternalError = 5,
-
-    #[display(fmt = "Sql internal error")]
-    SqlInternalError     = 6,
-
-    #[display(fmt = "r2d2 connection error")]
-    DatabaseConnectError = 7,
-
-    #[display(fmt = "User not login yet")]
-    UserNotLoginYet      = 10,
-    #[display(fmt = "Get current id read lock failed")]
-    ReadCurrentIdFailed  = 11,
-    #[display(fmt = "Get current id write lock failed")]
-    WriteCurrentIdFailed = 12,
 
     #[display(fmt = "Email can not be empty or whitespace")]
     EmailIsEmpty         = 20,
@@ -69,7 +54,7 @@ pub enum ErrorCode {
     #[display(fmt = "User name is too long")]
     UserNameTooLong      = 40,
     #[display(fmt = "User name contain forbidden characters")]
-    UserNameContainsForbiddenCharacters = 41,
+    ContainForbiddenCharacters = 41,
     #[display(fmt = "User name can not be empty or whitespace")]
     UserNameIsEmpty      = 42,
     #[display(fmt = "User workspace is invalid")]
@@ -77,18 +62,12 @@ pub enum ErrorCode {
     #[display(fmt = "User id is invalid")]
     UserIdInvalid        = 51,
     #[display(fmt = "User token is invalid")]
-    UserTokenInvalid     = 54,
+    UserUnauthorized     = 54,
     #[display(fmt = "User not exist")]
     UserNotExist         = 55,
 
-    #[display(fmt = "Create user default workspace failed")]
-    CreateDefaultWorkspaceFailed = 60,
-
-    #[display(fmt = "User default workspace already exists")]
-    DefaultWorkspaceAlreadyExist = 61,
-
-    #[display(fmt = "Server error")]
-    ServerError          = 100,
+    #[display(fmt = "Internal error")]
+    InternalError        = 100,
 }
 
 impl Debug for ErrorCode {
@@ -104,17 +83,17 @@ impl std::default::Default for ErrorCode {
 }
 
 impl std::convert::From<flowy_database::Error> for UserError {
-    fn from(error: flowy_database::Error) -> Self { ErrorBuilder::new(ErrorCode::UserDatabaseInternalError).error(error).build() }
+    fn from(error: flowy_database::Error) -> Self { ErrorBuilder::new(ErrorCode::InternalError).error(error).build() }
 }
 
 impl std::convert::From<::r2d2::Error> for UserError {
-    fn from(error: r2d2::Error) -> Self { ErrorBuilder::new(ErrorCode::DatabaseConnectError).error(error).build() }
+    fn from(error: r2d2::Error) -> Self { ErrorBuilder::new(ErrorCode::InternalError).error(error).build() }
 }
 
 // use diesel::result::{Error, DatabaseErrorKind};
 // use flowy_sqlite::ErrorKind;
 impl std::convert::From<flowy_sqlite::Error> for UserError {
-    fn from(error: flowy_sqlite::Error) -> Self { ErrorBuilder::new(ErrorCode::SqlInternalError).error(error).build() }
+    fn from(error: flowy_sqlite::Error) -> Self { ErrorBuilder::new(ErrorCode::InternalError).error(error).build() }
 }
 
 impl std::convert::From<flowy_net::errors::ServerError> for UserError {
@@ -127,24 +106,10 @@ impl std::convert::From<flowy_net::errors::ServerError> for UserError {
 use flowy_net::errors::ErrorCode as ServerErrorCode;
 fn server_error_to_user_error(code: ServerErrorCode) -> ErrorCode {
     match code {
-        ServerErrorCode::InvalidToken => ErrorCode::UserTokenInvalid,
-        ServerErrorCode::Unauthorized => ErrorCode::UserTokenInvalid,
-        ServerErrorCode::PayloadOverflow => ErrorCode::ServerError,
-        ServerErrorCode::PayloadSerdeFail => ErrorCode::ServerError,
-        ServerErrorCode::PayloadUnexpectedNone => ErrorCode::ServerError,
-        ServerErrorCode::ParamsInvalid => ErrorCode::ServerError,
-        ServerErrorCode::ProtobufError => ErrorCode::ServerError,
-        ServerErrorCode::SerdeError => ErrorCode::ServerError,
-        ServerErrorCode::EmailAlreadyExists => ErrorCode::ServerError,
+        ServerErrorCode::Unauthorized => ErrorCode::UserUnauthorized,
         ServerErrorCode::PasswordNotMatch => ErrorCode::PasswordNotMatch,
-        ServerErrorCode::ConnectRefused => ErrorCode::ServerError,
-        ServerErrorCode::ConnectTimeout => ErrorCode::ServerError,
-        ServerErrorCode::ConnectClose => ErrorCode::ServerError,
-        ServerErrorCode::ConnectCancel => ErrorCode::ServerError,
-        ServerErrorCode::SqlError => ErrorCode::ServerError,
         ServerErrorCode::RecordNotFound => ErrorCode::UserNotExist,
-        ServerErrorCode::HttpError => ErrorCode::ServerError,
-        ServerErrorCode::InternalError => ErrorCode::ServerError,
+        _ => ErrorCode::InternalError,
     }
 }
 

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

@@ -3,7 +3,7 @@ use flowy_dispatch::prelude::*;
 
 use std::{convert::TryInto, sync::Arc};
 
-#[tracing::instrument(name = "get_user_status", skip(session))]
+#[tracing::instrument(name = "get_profile", skip(session))]
 pub async fn user_profile_handler(session: Unit<Arc<UserSession>>) -> DataResult<UserProfile, UserError> {
     let user_profile = session.user_profile().await?;
     data_result(user_profile)

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

@@ -1,7 +1,6 @@
-use bytes::Bytes;
 use flowy_derive::ProtoBuf_Enum;
-use flowy_dispatch::prelude::ToBytes;
-use flowy_observable::{dart::RustStreamSender, entities::ObservableSubject, ObservableBuilder};
+
+use flowy_observable::ObservableBuilder;
 
 const OBSERVABLE_CATEGORY: &'static str = "User";
 
@@ -10,6 +9,7 @@ pub(crate) enum UserObservable {
     Unknown            = 0,
     UserAuthChanged    = 1,
     UserProfileUpdated = 2,
+    UserUnauthorized   = 3,
 }
 
 impl std::default::Default for UserObservable {

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

@@ -220,12 +220,6 @@ pub enum ErrorCode {
     AcquireWriteLockedFailed = 2,
     AcquireReadLockedFailed = 3,
     UserDatabaseDidNotMatch = 4,
-    UserDatabaseInternalError = 5,
-    SqlInternalError = 6,
-    DatabaseConnectError = 7,
-    UserNotLoginYet = 10,
-    ReadCurrentIdFailed = 11,
-    WriteCurrentIdFailed = 12,
     EmailIsEmpty = 20,
     EmailFormatInvalid = 21,
     EmailAlreadyExists = 22,
@@ -235,15 +229,13 @@ pub enum ErrorCode {
     PasswordFormatInvalid = 33,
     PasswordNotMatch = 34,
     UserNameTooLong = 40,
-    UserNameContainsForbiddenCharacters = 41,
+    ContainForbiddenCharacters = 41,
     UserNameIsEmpty = 42,
     UserWorkspaceInvalid = 50,
     UserIdInvalid = 51,
-    UserTokenInvalid = 54,
+    UserUnauthorized = 54,
     UserNotExist = 55,
-    CreateDefaultWorkspaceFailed = 60,
-    DefaultWorkspaceAlreadyExist = 61,
-    ServerError = 100,
+    InternalError = 100,
 }
 
 impl ::protobuf::ProtobufEnum for ErrorCode {
@@ -258,12 +250,6 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             2 => ::std::option::Option::Some(ErrorCode::AcquireWriteLockedFailed),
             3 => ::std::option::Option::Some(ErrorCode::AcquireReadLockedFailed),
             4 => ::std::option::Option::Some(ErrorCode::UserDatabaseDidNotMatch),
-            5 => ::std::option::Option::Some(ErrorCode::UserDatabaseInternalError),
-            6 => ::std::option::Option::Some(ErrorCode::SqlInternalError),
-            7 => ::std::option::Option::Some(ErrorCode::DatabaseConnectError),
-            10 => ::std::option::Option::Some(ErrorCode::UserNotLoginYet),
-            11 => ::std::option::Option::Some(ErrorCode::ReadCurrentIdFailed),
-            12 => ::std::option::Option::Some(ErrorCode::WriteCurrentIdFailed),
             20 => ::std::option::Option::Some(ErrorCode::EmailIsEmpty),
             21 => ::std::option::Option::Some(ErrorCode::EmailFormatInvalid),
             22 => ::std::option::Option::Some(ErrorCode::EmailAlreadyExists),
@@ -273,15 +259,13 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             33 => ::std::option::Option::Some(ErrorCode::PasswordFormatInvalid),
             34 => ::std::option::Option::Some(ErrorCode::PasswordNotMatch),
             40 => ::std::option::Option::Some(ErrorCode::UserNameTooLong),
-            41 => ::std::option::Option::Some(ErrorCode::UserNameContainsForbiddenCharacters),
+            41 => ::std::option::Option::Some(ErrorCode::ContainForbiddenCharacters),
             42 => ::std::option::Option::Some(ErrorCode::UserNameIsEmpty),
             50 => ::std::option::Option::Some(ErrorCode::UserWorkspaceInvalid),
             51 => ::std::option::Option::Some(ErrorCode::UserIdInvalid),
-            54 => ::std::option::Option::Some(ErrorCode::UserTokenInvalid),
+            54 => ::std::option::Option::Some(ErrorCode::UserUnauthorized),
             55 => ::std::option::Option::Some(ErrorCode::UserNotExist),
-            60 => ::std::option::Option::Some(ErrorCode::CreateDefaultWorkspaceFailed),
-            61 => ::std::option::Option::Some(ErrorCode::DefaultWorkspaceAlreadyExist),
-            100 => ::std::option::Option::Some(ErrorCode::ServerError),
+            100 => ::std::option::Option::Some(ErrorCode::InternalError),
             _ => ::std::option::Option::None
         }
     }
@@ -293,12 +277,6 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::AcquireWriteLockedFailed,
             ErrorCode::AcquireReadLockedFailed,
             ErrorCode::UserDatabaseDidNotMatch,
-            ErrorCode::UserDatabaseInternalError,
-            ErrorCode::SqlInternalError,
-            ErrorCode::DatabaseConnectError,
-            ErrorCode::UserNotLoginYet,
-            ErrorCode::ReadCurrentIdFailed,
-            ErrorCode::WriteCurrentIdFailed,
             ErrorCode::EmailIsEmpty,
             ErrorCode::EmailFormatInvalid,
             ErrorCode::EmailAlreadyExists,
@@ -308,15 +286,13 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::PasswordFormatInvalid,
             ErrorCode::PasswordNotMatch,
             ErrorCode::UserNameTooLong,
-            ErrorCode::UserNameContainsForbiddenCharacters,
+            ErrorCode::ContainForbiddenCharacters,
             ErrorCode::UserNameIsEmpty,
             ErrorCode::UserWorkspaceInvalid,
             ErrorCode::UserIdInvalid,
-            ErrorCode::UserTokenInvalid,
+            ErrorCode::UserUnauthorized,
             ErrorCode::UserNotExist,
-            ErrorCode::CreateDefaultWorkspaceFailed,
-            ErrorCode::DefaultWorkspaceAlreadyExist,
-            ErrorCode::ServerError,
+            ErrorCode::InternalError,
         ];
         values
     }
@@ -347,89 +323,69 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"=\n\tUserError\x12\x1e\n\x04code\x18\x01\x20\x01(\
     \x0e2\n.ErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03msg*\
-    \xe3\x05\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16UserDataba\
+    \x81\x04\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x1a\n\x16UserDataba\
     seInitFailed\x10\x01\x12\x1c\n\x18AcquireWriteLockedFailed\x10\x02\x12\
     \x1b\n\x17AcquireReadLockedFailed\x10\x03\x12\x1b\n\x17UserDatabaseDidNo\
-    tMatch\x10\x04\x12\x1d\n\x19UserDatabaseInternalError\x10\x05\x12\x14\n\
-    \x10SqlInternalError\x10\x06\x12\x18\n\x14DatabaseConnectError\x10\x07\
-    \x12\x13\n\x0fUserNotLoginYet\x10\n\x12\x17\n\x13ReadCurrentIdFailed\x10\
-    \x0b\x12\x18\n\x14WriteCurrentIdFailed\x10\x0c\x12\x10\n\x0cEmailIsEmpty\
-    \x10\x14\x12\x16\n\x12EmailFormatInvalid\x10\x15\x12\x16\n\x12EmailAlrea\
-    dyExists\x10\x16\x12\x13\n\x0fPasswordIsEmpty\x10\x1e\x12\x13\n\x0fPassw\
-    ordTooLong\x10\x1f\x12$\n\x20PasswordContainsForbidCharacters\x10\x20\
-    \x12\x19\n\x15PasswordFormatInvalid\x10!\x12\x14\n\x10PasswordNotMatch\
-    \x10\"\x12\x13\n\x0fUserNameTooLong\x10(\x12'\n#UserNameContainsForbidde\
-    nCharacters\x10)\x12\x13\n\x0fUserNameIsEmpty\x10*\x12\x18\n\x14UserWork\
-    spaceInvalid\x102\x12\x11\n\rUserIdInvalid\x103\x12\x14\n\x10UserTokenIn\
-    valid\x106\x12\x10\n\x0cUserNotExist\x107\x12\x20\n\x1cCreateDefaultWork\
-    spaceFailed\x10<\x12\x20\n\x1cDefaultWorkspaceAlreadyExist\x10=\x12\x0f\
-    \n\x0bServerError\x10dJ\xd5\n\n\x06\x12\x04\0\0$\x01\n\x08\n\x01\x0c\x12\
-    \x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\
-    \x12\x03\x02\x08\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x17\n\x0c\n\
-    \x05\x04\0\x02\0\x06\x12\x03\x03\x04\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\
-    \x03\x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x15\x16\n\x0b\n\
-    \x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\
-    \x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\
-    \x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\
-    $\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x0e\n\x0b\n\x04\x05\0\x02\0\
-    \x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\
-    \x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\
-    \x12\x03\x08\x04\x1f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x1a\n\
-    \x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\x04\x05\0\x02\
-    \x02\x12\x03\t\x04!\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x1c\n\
-    \x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x1f\x20\n\x0b\n\x04\x05\0\x02\x03\
-    \x12\x03\n\x04\x20\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x1b\n\x0c\
-    \n\x05\x05\0\x02\x03\x02\x12\x03\n\x1e\x1f\n\x0b\n\x04\x05\0\x02\x04\x12\
-    \x03\x0b\x04\x20\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x1b\n\x0c\
-    \n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\x05\0\x02\x05\
-    \x12\x03\x0c\x04\"\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x1d\n\
-    \x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x20!\n\x0b\n\x04\x05\0\x02\x06\
-    \x12\x03\r\x04\x19\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x14\n\x0c\
-    \n\x05\x05\0\x02\x06\x02\x12\x03\r\x17\x18\n\x0b\n\x04\x05\0\x02\x07\x12\
-    \x03\x0e\x04\x1d\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x18\n\x0c\
-    \n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x1b\x1c\n\x0b\n\x04\x05\0\x02\x08\
-    \x12\x03\x0f\x04\x19\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\x13\n\
-    \x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x16\x18\n\x0b\n\x04\x05\0\x02\t\
-    \x12\x03\x10\x04\x1d\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\x17\n\
-    \x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x1a\x1c\n\x0b\n\x04\x05\0\x02\n\
-    \x12\x03\x11\x04\x1e\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\x18\n\
-    \x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x1b\x1d\n\x0b\n\x04\x05\0\x02\x0b\
-    \x12\x03\x12\x04\x16\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\x04\x10\n\
-    \x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x13\x15\n\x0b\n\x04\x05\0\x02\
-    \x0c\x12\x03\x13\x04\x1c\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x13\x04\
-    \x16\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\x19\x1b\n\x0b\n\x04\x05\0\
-    \x02\r\x12\x03\x14\x04\x1c\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x14\x04\
-    \x16\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x19\x1b\n\x0b\n\x04\x05\0\
-    \x02\x0e\x12\x03\x15\x04\x19\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\x15\
-    \x04\x13\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\x15\x16\x18\n\x0b\n\x04\
-    \x05\0\x02\x0f\x12\x03\x16\x04\x19\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\x03\
-    \x16\x04\x13\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\x03\x16\x16\x18\n\x0b\n\
-    \x04\x05\0\x02\x10\x12\x03\x17\x04*\n\x0c\n\x05\x05\0\x02\x10\x01\x12\
-    \x03\x17\x04$\n\x0c\n\x05\x05\0\x02\x10\x02\x12\x03\x17')\n\x0b\n\x04\
-    \x05\0\x02\x11\x12\x03\x18\x04\x1f\n\x0c\n\x05\x05\0\x02\x11\x01\x12\x03\
-    \x18\x04\x19\n\x0c\n\x05\x05\0\x02\x11\x02\x12\x03\x18\x1c\x1e\n\x0b\n\
-    \x04\x05\0\x02\x12\x12\x03\x19\x04\x1a\n\x0c\n\x05\x05\0\x02\x12\x01\x12\
-    \x03\x19\x04\x14\n\x0c\n\x05\x05\0\x02\x12\x02\x12\x03\x19\x17\x19\n\x0b\
-    \n\x04\x05\0\x02\x13\x12\x03\x1a\x04\x19\n\x0c\n\x05\x05\0\x02\x13\x01\
-    \x12\x03\x1a\x04\x13\n\x0c\n\x05\x05\0\x02\x13\x02\x12\x03\x1a\x16\x18\n\
-    \x0b\n\x04\x05\0\x02\x14\x12\x03\x1b\x04-\n\x0c\n\x05\x05\0\x02\x14\x01\
-    \x12\x03\x1b\x04'\n\x0c\n\x05\x05\0\x02\x14\x02\x12\x03\x1b*,\n\x0b\n\
-    \x04\x05\0\x02\x15\x12\x03\x1c\x04\x19\n\x0c\n\x05\x05\0\x02\x15\x01\x12\
-    \x03\x1c\x04\x13\n\x0c\n\x05\x05\0\x02\x15\x02\x12\x03\x1c\x16\x18\n\x0b\
-    \n\x04\x05\0\x02\x16\x12\x03\x1d\x04\x1e\n\x0c\n\x05\x05\0\x02\x16\x01\
-    \x12\x03\x1d\x04\x18\n\x0c\n\x05\x05\0\x02\x16\x02\x12\x03\x1d\x1b\x1d\n\
-    \x0b\n\x04\x05\0\x02\x17\x12\x03\x1e\x04\x17\n\x0c\n\x05\x05\0\x02\x17\
-    \x01\x12\x03\x1e\x04\x11\n\x0c\n\x05\x05\0\x02\x17\x02\x12\x03\x1e\x14\
-    \x16\n\x0b\n\x04\x05\0\x02\x18\x12\x03\x1f\x04\x1a\n\x0c\n\x05\x05\0\x02\
-    \x18\x01\x12\x03\x1f\x04\x14\n\x0c\n\x05\x05\0\x02\x18\x02\x12\x03\x1f\
-    \x17\x19\n\x0b\n\x04\x05\0\x02\x19\x12\x03\x20\x04\x16\n\x0c\n\x05\x05\0\
-    \x02\x19\x01\x12\x03\x20\x04\x10\n\x0c\n\x05\x05\0\x02\x19\x02\x12\x03\
-    \x20\x13\x15\n\x0b\n\x04\x05\0\x02\x1a\x12\x03!\x04&\n\x0c\n\x05\x05\0\
-    \x02\x1a\x01\x12\x03!\x04\x20\n\x0c\n\x05\x05\0\x02\x1a\x02\x12\x03!#%\n\
-    \x0b\n\x04\x05\0\x02\x1b\x12\x03\"\x04&\n\x0c\n\x05\x05\0\x02\x1b\x01\
-    \x12\x03\"\x04\x20\n\x0c\n\x05\x05\0\x02\x1b\x02\x12\x03\"#%\n\x0b\n\x04\
-    \x05\0\x02\x1c\x12\x03#\x04\x16\n\x0c\n\x05\x05\0\x02\x1c\x01\x12\x03#\
-    \x04\x0f\n\x0c\n\x05\x05\0\x02\x1c\x02\x12\x03#\x12\x15b\x06proto3\
+    tMatch\x10\x04\x12\x10\n\x0cEmailIsEmpty\x10\x14\x12\x16\n\x12EmailForma\
+    tInvalid\x10\x15\x12\x16\n\x12EmailAlreadyExists\x10\x16\x12\x13\n\x0fPa\
+    sswordIsEmpty\x10\x1e\x12\x13\n\x0fPasswordTooLong\x10\x1f\x12$\n\x20Pas\
+    swordContainsForbidCharacters\x10\x20\x12\x19\n\x15PasswordFormatInvalid\
+    \x10!\x12\x14\n\x10PasswordNotMatch\x10\"\x12\x13\n\x0fUserNameTooLong\
+    \x10(\x12\x1e\n\x1aContainForbiddenCharacters\x10)\x12\x13\n\x0fUserName\
+    IsEmpty\x10*\x12\x18\n\x14UserWorkspaceInvalid\x102\x12\x11\n\rUserIdInv\
+    alid\x103\x12\x14\n\x10UserUnauthorized\x106\x12\x10\n\x0cUserNotExist\
+    \x107\x12\x11\n\rInternalError\x10dJ\x8d\x08\n\x06\x12\x04\0\0\x1c\x01\n\
+    \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\
+    \n\x03\x04\0\x01\x12\x03\x02\x08\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
+    \x04\x17\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\r\n\x0c\n\x05\x04\0\
+    \x02\0\x01\x12\x03\x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\
+    \x15\x16\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\
+    \x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\
+    \x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\
+    \0\x12\x04\x06\0\x1c\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x0e\n\x0b\n\
+    \x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\
+    \x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\
+    \x05\0\x02\x01\x12\x03\x08\x04\x1f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\
+    \x08\x04\x1a\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1d\x1e\n\x0b\n\
+    \x04\x05\0\x02\x02\x12\x03\t\x04!\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\
+    \t\x04\x1c\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x1f\x20\n\x0b\n\x04\
+    \x05\0\x02\x03\x12\x03\n\x04\x20\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\
+    \x04\x1b\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1e\x1f\n\x0b\n\x04\x05\
+    \0\x02\x04\x12\x03\x0b\x04\x20\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\
+    \x04\x1b\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1e\x1f\n\x0b\n\x04\
+    \x05\0\x02\x05\x12\x03\x0c\x04\x16\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\
+    \x0c\x04\x10\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x13\x15\n\x0b\n\
+    \x04\x05\0\x02\x06\x12\x03\r\x04\x1c\n\x0c\n\x05\x05\0\x02\x06\x01\x12\
+    \x03\r\x04\x16\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x19\x1b\n\x0b\n\
+    \x04\x05\0\x02\x07\x12\x03\x0e\x04\x1c\n\x0c\n\x05\x05\0\x02\x07\x01\x12\
+    \x03\x0e\x04\x16\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x19\x1b\n\x0b\
+    \n\x04\x05\0\x02\x08\x12\x03\x0f\x04\x19\n\x0c\n\x05\x05\0\x02\x08\x01\
+    \x12\x03\x0f\x04\x13\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x16\x18\n\
+    \x0b\n\x04\x05\0\x02\t\x12\x03\x10\x04\x19\n\x0c\n\x05\x05\0\x02\t\x01\
+    \x12\x03\x10\x04\x13\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x16\x18\n\
+    \x0b\n\x04\x05\0\x02\n\x12\x03\x11\x04*\n\x0c\n\x05\x05\0\x02\n\x01\x12\
+    \x03\x11\x04$\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11')\n\x0b\n\x04\x05\
+    \0\x02\x0b\x12\x03\x12\x04\x1f\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\
+    \x04\x19\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x1c\x1e\n\x0b\n\x04\
+    \x05\0\x02\x0c\x12\x03\x13\x04\x1a\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\
+    \x13\x04\x14\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\x17\x19\n\x0b\n\
+    \x04\x05\0\x02\r\x12\x03\x14\x04\x19\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\
+    \x14\x04\x13\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x16\x18\n\x0b\n\x04\
+    \x05\0\x02\x0e\x12\x03\x15\x04$\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\
+    \x15\x04\x1e\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\x15!#\n\x0b\n\x04\x05\
+    \0\x02\x0f\x12\x03\x16\x04\x19\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\x03\x16\
+    \x04\x13\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\x03\x16\x16\x18\n\x0b\n\x04\
+    \x05\0\x02\x10\x12\x03\x17\x04\x1e\n\x0c\n\x05\x05\0\x02\x10\x01\x12\x03\
+    \x17\x04\x18\n\x0c\n\x05\x05\0\x02\x10\x02\x12\x03\x17\x1b\x1d\n\x0b\n\
+    \x04\x05\0\x02\x11\x12\x03\x18\x04\x17\n\x0c\n\x05\x05\0\x02\x11\x01\x12\
+    \x03\x18\x04\x11\n\x0c\n\x05\x05\0\x02\x11\x02\x12\x03\x18\x14\x16\n\x0b\
+    \n\x04\x05\0\x02\x12\x12\x03\x19\x04\x1a\n\x0c\n\x05\x05\0\x02\x12\x01\
+    \x12\x03\x19\x04\x14\n\x0c\n\x05\x05\0\x02\x12\x02\x12\x03\x19\x17\x19\n\
+    \x0b\n\x04\x05\0\x02\x13\x12\x03\x1a\x04\x16\n\x0c\n\x05\x05\0\x02\x13\
+    \x01\x12\x03\x1a\x04\x10\n\x0c\n\x05\x05\0\x02\x13\x02\x12\x03\x1a\x13\
+    \x15\n\x0b\n\x04\x05\0\x02\x14\x12\x03\x1b\x04\x18\n\x0c\n\x05\x05\0\x02\
+    \x14\x01\x12\x03\x1b\x04\x11\n\x0c\n\x05\x05\0\x02\x14\x02\x12\x03\x1b\
+    \x14\x17b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 15 - 10
rust-lib/flowy-user/src/protobuf/model/observable.rs

@@ -28,6 +28,7 @@ pub enum UserObservable {
     Unknown = 0,
     UserAuthChanged = 1,
     UserProfileUpdated = 2,
+    UserUnauthorized = 3,
 }
 
 impl ::protobuf::ProtobufEnum for UserObservable {
@@ -40,6 +41,7 @@ impl ::protobuf::ProtobufEnum for UserObservable {
             0 => ::std::option::Option::Some(UserObservable::Unknown),
             1 => ::std::option::Option::Some(UserObservable::UserAuthChanged),
             2 => ::std::option::Option::Some(UserObservable::UserProfileUpdated),
+            3 => ::std::option::Option::Some(UserObservable::UserUnauthorized),
             _ => ::std::option::Option::None
         }
     }
@@ -49,6 +51,7 @@ impl ::protobuf::ProtobufEnum for UserObservable {
             UserObservable::Unknown,
             UserObservable::UserAuthChanged,
             UserObservable::UserProfileUpdated,
+            UserObservable::UserUnauthorized,
         ];
         values
     }
@@ -77,17 +80,19 @@ impl ::protobuf::reflect::ProtobufValue for UserObservable {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x10observable.proto*J\n\x0eUserObservable\x12\x0b\n\x07Unknown\x10\0\
+    \n\x10observable.proto*`\n\x0eUserObservable\x12\x0b\n\x07Unknown\x10\0\
     \x12\x13\n\x0fUserAuthChanged\x10\x01\x12\x16\n\x12UserProfileUpdated\
-    \x10\x02J\xa5\x01\n\x06\x12\x04\0\0\x05\x01\n\x08\n\x01\x0c\x12\x03\0\0\
-    \x12\n\n\n\x02\x05\0\x12\x04\x01\0\x05\x01\n\n\n\x03\x05\0\x01\x12\x03\
-    \x01\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\x02\x04\x10\n\x0c\n\x05\x05\
-    \0\x02\0\x01\x12\x03\x02\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x02\
-    \x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\
-    \x02\x01\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\
-    \x03\x16\x17\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x04\x04\x1b\n\x0c\n\x05\
-    \x05\0\x02\x02\x01\x12\x03\x04\x04\x16\n\x0c\n\x05\x05\0\x02\x02\x02\x12\
-    \x03\x04\x19\x1ab\x06proto3\
+    \x10\x02\x12\x14\n\x10UserUnauthorized\x10\x03J\xce\x01\n\x06\x12\x04\0\
+    \0\x07\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\
+    \x07\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\
+    \x12\x03\x03\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0b\n\
+    \x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x0e\x0f\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\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\
+    \x16\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\x19\x1a\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\x18b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -10,12 +10,6 @@ enum ErrorCode {
     AcquireWriteLockedFailed = 2;
     AcquireReadLockedFailed = 3;
     UserDatabaseDidNotMatch = 4;
-    UserDatabaseInternalError = 5;
-    SqlInternalError = 6;
-    DatabaseConnectError = 7;
-    UserNotLoginYet = 10;
-    ReadCurrentIdFailed = 11;
-    WriteCurrentIdFailed = 12;
     EmailIsEmpty = 20;
     EmailFormatInvalid = 21;
     EmailAlreadyExists = 22;
@@ -25,13 +19,11 @@ enum ErrorCode {
     PasswordFormatInvalid = 33;
     PasswordNotMatch = 34;
     UserNameTooLong = 40;
-    UserNameContainsForbiddenCharacters = 41;
+    ContainForbiddenCharacters = 41;
     UserNameIsEmpty = 42;
     UserWorkspaceInvalid = 50;
     UserIdInvalid = 51;
-    UserTokenInvalid = 54;
+    UserUnauthorized = 54;
     UserNotExist = 55;
-    CreateDefaultWorkspaceFailed = 60;
-    DefaultWorkspaceAlreadyExist = 61;
-    ServerError = 100;
+    InternalError = 100;
 }

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

@@ -1,6 +1,8 @@
 syntax = "proto3";
+
 enum UserObservable {
     Unknown = 0;
     UserAuthChanged = 1;
     UserProfileUpdated = 2;
+    UserUnauthorized = 3;
 }

+ 36 - 9
rust-lib/flowy-user/src/services/server/server_api.rs

@@ -5,7 +5,10 @@ use crate::{
 
 use crate::{entities::UpdateUserParams, services::server::UserServerAPI};
 use flowy_infra::future::ResultFuture;
-use flowy_net::{config::*, request::HttpRequestBuilder};
+use flowy_net::{
+    config::*,
+    request::{HttpRequestBuilder, ResponseMiddleware},
+};
 
 pub struct UserServer {}
 impl UserServer {
@@ -40,8 +43,32 @@ impl UserServerAPI for UserServer {
     }
 }
 
+use crate::{errors::ErrorCode, observable::*};
+use flowy_net::response::FlowyResponse;
+use lazy_static::lazy_static;
+use std::sync::Arc;
+lazy_static! {
+    static ref IDDLEWARE: Arc<Middleware> = Arc::new(Middleware {});
+}
+
+struct Middleware {}
+impl ResponseMiddleware for Middleware {
+    fn receive_response(&self, response: &FlowyResponse) {
+        if let Some(error) = &response.error {
+            if error.is_unauthorized() {
+                log::error!("user unauthorized");
+                let error = UserError::new(ErrorCode::UserUnauthorized, "");
+                observable("", UserObservable::UserUnauthorized).error(error).build()
+            }
+        }
+    }
+}
+
+pub(crate) fn request_builder() -> HttpRequestBuilder { HttpRequestBuilder::new().middleware(IDDLEWARE.clone()) }
+
 pub async fn user_sign_up_request(params: SignUpParams, url: &str) -> Result<SignUpResponse, UserError> {
-    let response = HttpRequestBuilder::post(&url.to_owned())
+    let response = request_builder()
+        .post(&url.to_owned())
         .protobuf(params)?
         .send()
         .await?
@@ -51,7 +78,8 @@ pub async fn user_sign_up_request(params: SignUpParams, url: &str) -> Result<Sig
 }
 
 pub async fn user_sign_in_request(params: SignInParams, url: &str) -> Result<SignInResponse, UserError> {
-    let response = HttpRequestBuilder::post(&url.to_owned())
+    let response = request_builder()
+        .post(&url.to_owned())
         .protobuf(params)?
         .send()
         .await?
@@ -61,15 +89,13 @@ pub async fn user_sign_in_request(params: SignInParams, url: &str) -> Result<Sig
 }
 
 pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), UserError> {
-    let _ = HttpRequestBuilder::delete(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .send()
-        .await?;
+    let _ = request_builder().delete(&url.to_owned()).header(HEADER_TOKEN, token).send().await?;
     Ok(())
 }
 
 pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfile, UserError> {
-    let user_profile = HttpRequestBuilder::get(&url.to_owned())
+    let user_profile = request_builder()
+        .get(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .send()
         .await?
@@ -79,7 +105,8 @@ pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProf
 }
 
 pub async fn update_user_profile_request(token: &str, params: UpdateUserParams, url: &str) -> Result<(), UserError> {
-    let _ = HttpRequestBuilder::patch(&url.to_owned())
+    let _ = request_builder()
+        .patch(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()

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

@@ -193,7 +193,7 @@ impl UserSession {
     fn set_session(&self, session: Option<Session>) -> Result<(), UserError> {
         log::debug!("Set user session: {:?}", session);
         match &session {
-            None => KV::remove(SESSION_CACHE_KEY).map_err(|e| UserError::new(ErrorCode::SqlInternalError, &e))?,
+            None => KV::remove(SESSION_CACHE_KEY).map_err(|e| UserError::new(ErrorCode::InternalError, &e))?,
             Some(session) => KV::set_str(SESSION_CACHE_KEY, session.clone().into()),
         }
         *self.session.write() = session;
@@ -213,7 +213,7 @@ impl UserSession {
         }
 
         match session {
-            None => Err(ErrorBuilder::new(ErrorCode::UserNotLoginYet).build()),
+            None => Err(ErrorBuilder::new(ErrorCode::UserUnauthorized).build()),
             Some(session) => Ok(session),
         }
     }
@@ -255,7 +255,7 @@ impl Session {
         }
     }
 
-    pub fn into_part(mut self) -> (String, String) { (self.user_id, self.token) }
+    pub fn into_part(self) -> (String, String) { (self.user_id, self.token) }
 }
 
 impl std::convert::From<String> for Session {

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

@@ -13,7 +13,7 @@ flowy-database = { path = "../flowy-database" }
 flowy-sqlite = { path = "../flowy-sqlite" }
 flowy-infra = { path = "../flowy-infra" }
 flowy-observable = { path = "../flowy-observable" }
-flowy-net = { path = "../flowy-net" }
+flowy-net = { path = "../flowy-net", features = ["flowy_request"] }
 
 protobuf = {version = "2.18.0"}
 log = "0.4.14"

+ 17 - 14
rust-lib/flowy-workspace/src/errors.rs

@@ -2,7 +2,7 @@ use bytes::Bytes;
 use derive_more::Display;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_dispatch::prelude::{EventResponse, ResponseBuilder};
-use flowy_net::errors::ErrorCode as NetworkErrorCode;
+
 use std::{convert::TryInto, fmt};
 
 #[derive(Debug, Default, Clone, ProtoBuf)]
@@ -62,17 +62,14 @@ pub enum ErrorCode {
     #[display(fmt = "Database internal error")]
     WorkspaceDatabaseError = 101,
 
-    #[display(fmt = "User internal error")]
-    UserInternalError    = 102,
-
-    #[display(fmt = "User not login yet")]
-    UserNotLoginYet      = 103,
-
     #[display(fmt = "UserIn is empty")]
-    UserIdIsEmpty        = 104,
+    UserIdIsEmpty        = 102,
+
+    #[display(fmt = "User unauthorized")]
+    UserUnauthorized     = 103,
 
     #[display(fmt = "Server error")]
-    ServerError          = 1000,
+    InternalError        = 1000,
     #[display(fmt = "Record not found")]
     RecordNotFound       = 1001,
 }
@@ -83,11 +80,8 @@ impl std::default::Default for ErrorCode {
 
 impl std::convert::From<flowy_net::errors::ServerError> for WorkspaceError {
     fn from(error: flowy_net::errors::ServerError) -> Self {
-        match error.code {
-            NetworkErrorCode::RecordNotFound => ErrorBuilder::new(ErrorCode::RecordNotFound).error(error.msg).build(),
-
-            _ => ErrorBuilder::new(ErrorCode::ServerError).error(error.msg).build(),
-        }
+        let code = server_error_to_workspace_error(error.code);
+        ErrorBuilder::new(code).error(error.msg).build()
     }
 }
 
@@ -111,3 +105,12 @@ pub type ErrorBuilder = flowy_infra::errors::Builder<ErrorCode, WorkspaceError>;
 impl flowy_infra::errors::Build<ErrorCode> for WorkspaceError {
     fn build(code: ErrorCode, msg: String) -> Self { WorkspaceError::new(code, &msg) }
 }
+
+use flowy_net::errors::ErrorCode as ServerErrorCode;
+fn server_error_to_workspace_error(code: ServerErrorCode) -> ErrorCode {
+    match code {
+        ServerErrorCode::Unauthorized => ErrorCode::UserUnauthorized,
+        ServerErrorCode::RecordNotFound => ErrorCode::RecordNotFound,
+        _ => ErrorCode::InternalError,
+    }
+}

+ 4 - 3
rust-lib/flowy-workspace/src/observable/observable.rs

@@ -1,7 +1,6 @@
-use bytes::Bytes;
 use flowy_derive::ProtoBuf_Enum;
-use flowy_dispatch::prelude::ToBytes;
-use flowy_observable::{dart::RustStreamSender, entities::ObservableSubject, ObservableBuilder};
+
+use flowy_observable::ObservableBuilder;
 
 const OBSERVABLE_CATEGORY: &'static str = "Workspace";
 
@@ -22,6 +21,8 @@ pub(crate) enum WorkspaceObservable {
     AppDeleteView        = 24,
 
     ViewUpdated          = 31,
+
+    UserUnauthorized     = 100,
 }
 
 impl std::default::Default for WorkspaceObservable {

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

@@ -229,10 +229,9 @@ pub enum ErrorCode {
     ViewDescInvalid = 23,
     DatabaseConnectionFail = 100,
     WorkspaceDatabaseError = 101,
-    UserInternalError = 102,
-    UserNotLoginYet = 103,
-    UserIdIsEmpty = 104,
-    ServerError = 1000,
+    UserIdIsEmpty = 102,
+    UserUnauthorized = 103,
+    InternalError = 1000,
     RecordNotFound = 1001,
 }
 
@@ -257,10 +256,9 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             23 => ::std::option::Option::Some(ErrorCode::ViewDescInvalid),
             100 => ::std::option::Option::Some(ErrorCode::DatabaseConnectionFail),
             101 => ::std::option::Option::Some(ErrorCode::WorkspaceDatabaseError),
-            102 => ::std::option::Option::Some(ErrorCode::UserInternalError),
-            103 => ::std::option::Option::Some(ErrorCode::UserNotLoginYet),
-            104 => ::std::option::Option::Some(ErrorCode::UserIdIsEmpty),
-            1000 => ::std::option::Option::Some(ErrorCode::ServerError),
+            102 => ::std::option::Option::Some(ErrorCode::UserIdIsEmpty),
+            103 => ::std::option::Option::Some(ErrorCode::UserUnauthorized),
+            1000 => ::std::option::Option::Some(ErrorCode::InternalError),
             1001 => ::std::option::Option::Some(ErrorCode::RecordNotFound),
             _ => ::std::option::Option::None
         }
@@ -282,10 +280,9 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::ViewDescInvalid,
             ErrorCode::DatabaseConnectionFail,
             ErrorCode::WorkspaceDatabaseError,
-            ErrorCode::UserInternalError,
-            ErrorCode::UserNotLoginYet,
             ErrorCode::UserIdIsEmpty,
-            ErrorCode::ServerError,
+            ErrorCode::UserUnauthorized,
+            ErrorCode::InternalError,
             ErrorCode::RecordNotFound,
         ];
         values
@@ -317,64 +314,61 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x0cerrors.proto\"B\n\x0eWorkspaceError\x12\x1e\n\x04code\x18\x01\x20\
     \x01(\x0e2\n.ErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03\
-    msg*\xb7\x03\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x18\n\x14Worksp\
+    msg*\xa3\x03\n\tErrorCode\x12\x0b\n\x07Unknown\x10\0\x12\x18\n\x14Worksp\
     aceNameInvalid\x10\x01\x12\x16\n\x12WorkspaceIdInvalid\x10\x02\x12\x18\n\
     \x14AppColorStyleInvalid\x10\x03\x12\x18\n\x14WorkspaceDescInvalid\x10\
     \x04\x12\x1c\n\x18CurrentWorkspaceNotFound\x10\x05\x12\x10\n\x0cAppIdInv\
     alid\x10\n\x12\x12\n\x0eAppNameInvalid\x10\x0b\x12\x13\n\x0fViewNameInva\
     lid\x10\x14\x12\x18\n\x14ViewThumbnailInvalid\x10\x15\x12\x11\n\rViewIdI\
     nvalid\x10\x16\x12\x13\n\x0fViewDescInvalid\x10\x17\x12\x1a\n\x16Databas\
-    eConnectionFail\x10d\x12\x1a\n\x16WorkspaceDatabaseError\x10e\x12\x15\n\
-    \x11UserInternalError\x10f\x12\x13\n\x0fUserNotLoginYet\x10g\x12\x11\n\r\
-    UserIdIsEmpty\x10h\x12\x10\n\x0bServerError\x10\xe8\x07\x12\x13\n\x0eRec\
-    ordNotFound\x10\xe9\x07J\xbb\x07\n\x06\x12\x04\0\0\x1a\x01\n\x08\n\x01\
-    \x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\
-    \0\x01\x12\x03\x02\x08\x16\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x17\n\
-    \x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\x04\r\n\x0c\n\x05\x04\0\x02\0\x01\
-    \x12\x03\x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x15\x16\n\
-    \x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\
-    \x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\
-    \n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\
-    \x04\x06\0\x1a\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x0e\n\x0b\n\x04\
-    \x05\0\x02\0\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\
-    \x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\
-    \0\x02\x01\x12\x03\x08\x04\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\
-    \x04\x18\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x1b\x1c\n\x0b\n\x04\
-    \x05\0\x02\x02\x12\x03\t\x04\x1b\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\
-    \x04\x16\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\t\x19\x1a\n\x0b\n\x04\x05\
-    \0\x02\x03\x12\x03\n\x04\x1d\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\
-    \x18\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\n\x1b\x1c\n\x0b\n\x04\x05\0\
-    \x02\x04\x12\x03\x0b\x04\x1d\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\
-    \x04\x18\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x1b\x1c\n\x0b\n\x04\
-    \x05\0\x02\x05\x12\x03\x0c\x04!\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\
-    \x0c\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x1f\x20\n\x0b\n\
-    \x04\x05\0\x02\x06\x12\x03\r\x04\x16\n\x0c\n\x05\x05\0\x02\x06\x01\x12\
-    \x03\r\x04\x10\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x13\x15\n\x0b\n\
-    \x04\x05\0\x02\x07\x12\x03\x0e\x04\x18\n\x0c\n\x05\x05\0\x02\x07\x01\x12\
-    \x03\x0e\x04\x12\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x15\x17\n\x0b\
-    \n\x04\x05\0\x02\x08\x12\x03\x0f\x04\x19\n\x0c\n\x05\x05\0\x02\x08\x01\
-    \x12\x03\x0f\x04\x13\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0f\x16\x18\n\
-    \x0b\n\x04\x05\0\x02\t\x12\x03\x10\x04\x1e\n\x0c\n\x05\x05\0\x02\t\x01\
-    \x12\x03\x10\x04\x18\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x10\x1b\x1d\n\
-    \x0b\n\x04\x05\0\x02\n\x12\x03\x11\x04\x17\n\x0c\n\x05\x05\0\x02\n\x01\
-    \x12\x03\x11\x04\x11\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\x11\x14\x16\n\
-    \x0b\n\x04\x05\0\x02\x0b\x12\x03\x12\x04\x19\n\x0c\n\x05\x05\0\x02\x0b\
-    \x01\x12\x03\x12\x04\x13\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x12\x16\
-    \x18\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x13\x04!\n\x0c\n\x05\x05\0\x02\
-    \x0c\x01\x12\x03\x13\x04\x1a\n\x0c\n\x05\x05\0\x02\x0c\x02\x12\x03\x13\
-    \x1d\x20\n\x0b\n\x04\x05\0\x02\r\x12\x03\x14\x04!\n\x0c\n\x05\x05\0\x02\
-    \r\x01\x12\x03\x14\x04\x1a\n\x0c\n\x05\x05\0\x02\r\x02\x12\x03\x14\x1d\
-    \x20\n\x0b\n\x04\x05\0\x02\x0e\x12\x03\x15\x04\x1c\n\x0c\n\x05\x05\0\x02\
-    \x0e\x01\x12\x03\x15\x04\x15\n\x0c\n\x05\x05\0\x02\x0e\x02\x12\x03\x15\
-    \x18\x1b\n\x0b\n\x04\x05\0\x02\x0f\x12\x03\x16\x04\x1a\n\x0c\n\x05\x05\0\
-    \x02\x0f\x01\x12\x03\x16\x04\x13\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\x03\
-    \x16\x16\x19\n\x0b\n\x04\x05\0\x02\x10\x12\x03\x17\x04\x18\n\x0c\n\x05\
-    \x05\0\x02\x10\x01\x12\x03\x17\x04\x11\n\x0c\n\x05\x05\0\x02\x10\x02\x12\
-    \x03\x17\x14\x17\n\x0b\n\x04\x05\0\x02\x11\x12\x03\x18\x04\x17\n\x0c\n\
-    \x05\x05\0\x02\x11\x01\x12\x03\x18\x04\x0f\n\x0c\n\x05\x05\0\x02\x11\x02\
-    \x12\x03\x18\x12\x16\n\x0b\n\x04\x05\0\x02\x12\x12\x03\x19\x04\x1a\n\x0c\
-    \n\x05\x05\0\x02\x12\x01\x12\x03\x19\x04\x12\n\x0c\n\x05\x05\0\x02\x12\
-    \x02\x12\x03\x19\x15\x19b\x06proto3\
+    eConnectionFail\x10d\x12\x1a\n\x16WorkspaceDatabaseError\x10e\x12\x11\n\
+    \rUserIdIsEmpty\x10f\x12\x14\n\x10UserUnauthorized\x10g\x12\x12\n\rInter\
+    nalError\x10\xe8\x07\x12\x13\n\x0eRecordNotFound\x10\xe9\x07J\x92\x07\n\
+    \x06\x12\x04\0\0\x19\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\
+    \x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x16\n\x0b\n\
+    \x04\x04\0\x02\0\x12\x03\x03\x04\x17\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\
+    \x03\x04\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0e\x12\n\x0c\n\x05\
+    \x04\0\x02\0\x03\x12\x03\x03\x15\x16\n\x0b\n\x04\x04\0\x02\x01\x12\x03\
+    \x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\
+    \x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\
+    \x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x19\x01\n\n\n\x03\x05\0\
+    \x01\x12\x03\x06\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
+    \x02\x12\x03\x07\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x18\n\x0c\n\x05\x05\0\x02\
+    \x01\x02\x12\x03\x08\x1b\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x1b\
+    \n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x16\n\x0c\n\x05\x05\0\x02\
+    \x02\x02\x12\x03\t\x19\x1a\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x18\n\x0c\n\x05\x05\0\x02\x03\
+    \x02\x12\x03\n\x1b\x1c\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x18\n\x0c\n\x05\x05\0\x02\
+    \x04\x02\x12\x03\x0b\x1b\x1c\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x0c\x04!\
+    \n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x1c\n\x0c\n\x05\x05\0\x02\
+    \x05\x02\x12\x03\x0c\x1f\x20\n\x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x16\
+    \n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x10\n\x0c\n\x05\x05\0\x02\
+    \x06\x02\x12\x03\r\x13\x15\n\x0b\n\x04\x05\0\x02\x07\x12\x03\x0e\x04\x18\
+    \n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x12\n\x0c\n\x05\x05\0\x02\
+    \x07\x02\x12\x03\x0e\x15\x17\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\x04\
+    \x19\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\x13\n\x0c\n\x05\x05\0\
+    \x02\x08\x02\x12\x03\x0f\x16\x18\n\x0b\n\x04\x05\0\x02\t\x12\x03\x10\x04\
+    \x1e\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x10\x04\x18\n\x0c\n\x05\x05\0\
+    \x02\t\x02\x12\x03\x10\x1b\x1d\n\x0b\n\x04\x05\0\x02\n\x12\x03\x11\x04\
+    \x17\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\x11\x04\x11\n\x0c\n\x05\x05\0\
+    \x02\n\x02\x12\x03\x11\x14\x16\n\x0b\n\x04\x05\0\x02\x0b\x12\x03\x12\x04\
+    \x19\n\x0c\n\x05\x05\0\x02\x0b\x01\x12\x03\x12\x04\x13\n\x0c\n\x05\x05\0\
+    \x02\x0b\x02\x12\x03\x12\x16\x18\n\x0b\n\x04\x05\0\x02\x0c\x12\x03\x13\
+    \x04!\n\x0c\n\x05\x05\0\x02\x0c\x01\x12\x03\x13\x04\x1a\n\x0c\n\x05\x05\
+    \0\x02\x0c\x02\x12\x03\x13\x1d\x20\n\x0b\n\x04\x05\0\x02\r\x12\x03\x14\
+    \x04!\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x14\x04\x1a\n\x0c\n\x05\x05\0\
+    \x02\r\x02\x12\x03\x14\x1d\x20\n\x0b\n\x04\x05\0\x02\x0e\x12\x03\x15\x04\
+    \x18\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\x15\x04\x11\n\x0c\n\x05\x05\0\
+    \x02\x0e\x02\x12\x03\x15\x14\x17\n\x0b\n\x04\x05\0\x02\x0f\x12\x03\x16\
+    \x04\x1b\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\x03\x16\x04\x14\n\x0c\n\x05\
+    \x05\0\x02\x0f\x02\x12\x03\x16\x17\x1a\n\x0b\n\x04\x05\0\x02\x10\x12\x03\
+    \x17\x04\x19\n\x0c\n\x05\x05\0\x02\x10\x01\x12\x03\x17\x04\x11\n\x0c\n\
+    \x05\x05\0\x02\x10\x02\x12\x03\x17\x14\x18\n\x0b\n\x04\x05\0\x02\x11\x12\
+    \x03\x18\x04\x1a\n\x0c\n\x05\x05\0\x02\x11\x01\x12\x03\x18\x04\x12\n\x0c\
+    \n\x05\x05\0\x02\x11\x02\x12\x03\x18\x15\x19b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 32 - 26
rust-lib/flowy-workspace/src/protobuf/model/observable.rs

@@ -36,6 +36,7 @@ pub enum WorkspaceObservable {
     AppCreateView = 23,
     AppDeleteView = 24,
     ViewUpdated = 31,
+    UserUnauthorized = 100,
 }
 
 impl ::protobuf::ProtobufEnum for WorkspaceObservable {
@@ -56,6 +57,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceObservable {
             23 => ::std::option::Option::Some(WorkspaceObservable::AppCreateView),
             24 => ::std::option::Option::Some(WorkspaceObservable::AppDeleteView),
             31 => ::std::option::Option::Some(WorkspaceObservable::ViewUpdated),
+            100 => ::std::option::Option::Some(WorkspaceObservable::UserUnauthorized),
             _ => ::std::option::Option::None
         }
     }
@@ -73,6 +75,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceObservable {
             WorkspaceObservable::AppCreateView,
             WorkspaceObservable::AppDeleteView,
             WorkspaceObservable::ViewUpdated,
+            WorkspaceObservable::UserUnauthorized,
         ];
         values
     }
@@ -101,37 +104,40 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceObservable {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x10observable.proto*\xfb\x01\n\x13WorkspaceObservable\x12\x0b\n\x07Un\
+    \n\x10observable.proto*\x91\x02\n\x13WorkspaceObservable\x12\x0b\n\x07Un\
     known\x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13UserDele\
     teWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x16\n\x12W\
     orkspaceCreateApp\x10\r\x12\x16\n\x12WorkspaceDeleteApp\x10\x0e\x12\x18\
     \n\x14WorkspaceListUpdated\x10\x0f\x12\x0e\n\nAppUpdated\x10\x15\x12\x11\
     \n\rAppCreateView\x10\x17\x12\x11\n\rAppDeleteView\x10\x18\x12\x0f\n\x0b\
-    ViewUpdated\x10\x1fJ\xed\x03\n\x06\x12\x04\0\0\x0e\x01\n\x08\n\x01\x0c\
-    \x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x0e\x01\n\n\n\x03\x05\0\
-    \x01\x12\x03\x02\x05\x18\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x10\n\
-    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
-    \x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x1d\n\
-    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x17\n\x0c\n\x05\x05\0\x02\
-    \x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\
-    \x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x17\n\x0c\n\x05\x05\0\
-    \x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\
-    \x04\x1a\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\x19\n\x0b\n\x04\x05\0\x02\x04\x12\x03\
-    \x07\x04\x1c\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x16\n\x0c\n\
-    \x05\x05\0\x02\x04\x02\x12\x03\x07\x19\x1b\n\x0b\n\x04\x05\0\x02\x05\x12\
-    \x03\x08\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x16\n\x0c\
-    \n\x05\x05\0\x02\x05\x02\x12\x03\x08\x19\x1b\n\x0b\n\x04\x05\0\x02\x06\
-    \x12\x03\t\x04\x1e\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\x18\n\x0c\
-    \n\x05\x05\0\x02\x06\x02\x12\x03\t\x1b\x1d\n\x0b\n\x04\x05\0\x02\x07\x12\
-    \x03\n\x04\x14\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\x13\n\x0b\n\x04\x05\0\x02\x08\x12\
-    \x03\x0b\x04\x17\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\x04\x11\n\x0c\
-    \n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x14\x16\n\x0b\n\x04\x05\0\x02\t\x12\
-    \x03\x0c\x04\x17\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x0c\x04\x11\n\x0c\n\
-    \x05\x05\0\x02\t\x02\x12\x03\x0c\x14\x16\n\x0b\n\x04\x05\0\x02\n\x12\x03\
-    \r\x04\x15\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\r\x04\x0f\n\x0c\n\x05\x05\
-    \0\x02\n\x02\x12\x03\r\x12\x14b\x06proto3\
+    ViewUpdated\x10\x1f\x12\x14\n\x10UserUnauthorized\x10dJ\x96\x04\n\x06\
+    \x12\x04\0\0\x0f\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\
+    \x04\x02\0\x0f\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x18\n\x0b\n\x04\
+    \x05\0\x02\0\x12\x03\x03\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\
+    \x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\
+    \0\x02\x01\x12\x03\x04\x04\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\
+    \x04\x17\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\
+    \x05\0\x02\x02\x12\x03\x05\x04\x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\
+    \x05\x04\x17\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\
+    \x04\x05\0\x02\x03\x12\x03\x06\x04\x1a\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\x19\n\x0b\
+    \n\x04\x05\0\x02\x04\x12\x03\x07\x04\x1c\n\x0c\n\x05\x05\0\x02\x04\x01\
+    \x12\x03\x07\x04\x16\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x19\x1b\n\
+    \x0b\n\x04\x05\0\x02\x05\x12\x03\x08\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\
+    \x01\x12\x03\x08\x04\x16\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\x19\
+    \x1b\n\x0b\n\x04\x05\0\x02\x06\x12\x03\t\x04\x1e\n\x0c\n\x05\x05\0\x02\
+    \x06\x01\x12\x03\t\x04\x18\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x1b\
+    \x1d\n\x0b\n\x04\x05\0\x02\x07\x12\x03\n\x04\x14\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\
+    \x13\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0b\x04\x17\n\x0c\n\x05\x05\0\x02\
+    \x08\x01\x12\x03\x0b\x04\x11\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0b\
+    \x14\x16\n\x0b\n\x04\x05\0\x02\t\x12\x03\x0c\x04\x17\n\x0c\n\x05\x05\0\
+    \x02\t\x01\x12\x03\x0c\x04\x11\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x0c\
+    \x14\x16\n\x0b\n\x04\x05\0\x02\n\x12\x03\r\x04\x15\n\x0c\n\x05\x05\0\x02\
+    \n\x01\x12\x03\r\x04\x0f\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\r\x12\x14\n\
+    \x0b\n\x04\x05\0\x02\x0b\x12\x03\x0e\x04\x1b\n\x0c\n\x05\x05\0\x02\x0b\
+    \x01\x12\x03\x0e\x04\x14\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x0e\x17\
+    \x1ab\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -19,9 +19,8 @@ enum ErrorCode {
     ViewDescInvalid = 23;
     DatabaseConnectionFail = 100;
     WorkspaceDatabaseError = 101;
-    UserInternalError = 102;
-    UserNotLoginYet = 103;
-    UserIdIsEmpty = 104;
-    ServerError = 1000;
+    UserIdIsEmpty = 102;
+    UserUnauthorized = 103;
+    InternalError = 1000;
     RecordNotFound = 1001;
 }

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

@@ -12,4 +12,5 @@ enum WorkspaceObservable {
     AppCreateView = 23;
     AppDeleteView = 24;
     ViewUpdated = 31;
+    UserUnauthorized = 100;
 }

+ 53 - 13
rust-lib/flowy-workspace/src/services/server/server_api.rs

@@ -15,7 +15,14 @@ use crate::{
     services::server::WorkspaceServerAPI,
 };
 use flowy_infra::future::ResultFuture;
-use flowy_net::{config::*, request::HttpRequestBuilder};
+use flowy_net::{
+    config::*,
+    request::{HttpRequestBuilder, ResponseMiddleware},
+    response::FlowyResponse,
+};
+use lazy_static::lazy_static;
+use std::sync::Arc;
+
 pub struct WorkspaceServer {}
 
 impl WorkspaceServerAPI for WorkspaceServer {
@@ -80,8 +87,30 @@ impl WorkspaceServerAPI for WorkspaceServer {
     }
 }
 
+lazy_static! {
+    static ref MIDDLEWARE: Arc<WorkspaceMiddleware> = Arc::new(WorkspaceMiddleware {});
+}
+
+use crate::{errors::ErrorCode, observable::*};
+
+struct WorkspaceMiddleware {}
+impl ResponseMiddleware for WorkspaceMiddleware {
+    fn receive_response(&self, response: &FlowyResponse) {
+        if let Some(error) = &response.error {
+            if error.is_unauthorized() {
+                log::error!("workspace user is unauthorized");
+                let error = WorkspaceError::new(ErrorCode::UserUnauthorized, "");
+                observable("", WorkspaceObservable::UserUnauthorized).error(error).build()
+            }
+        }
+    }
+}
+
+pub(crate) fn request_builder() -> HttpRequestBuilder { HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) }
+
 pub async fn create_workspace_request(token: &str, params: CreateWorkspaceParams, url: &str) -> Result<Workspace, WorkspaceError> {
-    let workspace = HttpRequestBuilder::post(&url.to_owned())
+    let workspace = request_builder()
+        .post(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -92,7 +121,8 @@ pub async fn create_workspace_request(token: &str, params: CreateWorkspaceParams
 }
 
 pub async fn read_workspaces_request(token: &str, params: QueryWorkspaceParams, url: &str) -> Result<RepeatedWorkspace, WorkspaceError> {
-    let result = HttpRequestBuilder::get(&url.to_owned())
+    let result = request_builder()
+        .get(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -107,7 +137,8 @@ pub async fn read_workspaces_request(token: &str, params: QueryWorkspaceParams,
 }
 
 pub async fn update_workspace_request(token: &str, params: UpdateWorkspaceParams, url: &str) -> Result<(), WorkspaceError> {
-    let _ = HttpRequestBuilder::patch(&url.to_owned())
+    let _ = request_builder()
+        .patch(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -116,7 +147,8 @@ pub async fn update_workspace_request(token: &str, params: UpdateWorkspaceParams
 }
 
 pub async fn delete_workspace_request(token: &str, params: DeleteWorkspaceParams, url: &str) -> Result<(), WorkspaceError> {
-    let _ = HttpRequestBuilder::delete(url)
+    let _ = request_builder()
+        .delete(url)
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -126,7 +158,8 @@ pub async fn delete_workspace_request(token: &str, params: DeleteWorkspaceParams
 
 // App
 pub async fn create_app_request(token: &str, params: CreateAppParams, url: &str) -> Result<App, WorkspaceError> {
-    let app = HttpRequestBuilder::post(&url.to_owned())
+    let app = request_builder()
+        .post(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -137,7 +170,8 @@ pub async fn create_app_request(token: &str, params: CreateAppParams, url: &str)
 }
 
 pub async fn read_app_request(token: &str, params: QueryAppParams, url: &str) -> Result<Option<App>, WorkspaceError> {
-    let result = HttpRequestBuilder::get(&url.to_owned())
+    let result = request_builder()
+        .get(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -156,7 +190,8 @@ pub async fn read_app_request(token: &str, params: QueryAppParams, url: &str) ->
 }
 
 pub async fn update_app_request(token: &str, params: UpdateAppParams, url: &str) -> Result<(), WorkspaceError> {
-    let _ = HttpRequestBuilder::patch(&url.to_owned())
+    let _ = request_builder()
+        .patch(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -165,7 +200,8 @@ pub async fn update_app_request(token: &str, params: UpdateAppParams, url: &str)
 }
 
 pub async fn delete_app_request(token: &str, params: DeleteAppParams, url: &str) -> Result<(), WorkspaceError> {
-    let _ = HttpRequestBuilder::delete(&url.to_owned())
+    let _ = request_builder()
+        .delete(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -175,7 +211,8 @@ pub async fn delete_app_request(token: &str, params: DeleteAppParams, url: &str)
 
 // View
 pub async fn create_view_request(token: &str, params: CreateViewParams, url: &str) -> Result<View, WorkspaceError> {
-    let view = HttpRequestBuilder::post(&url.to_owned())
+    let view = request_builder()
+        .post(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -186,7 +223,8 @@ pub async fn create_view_request(token: &str, params: CreateViewParams, url: &st
 }
 
 pub async fn read_view_request(token: &str, params: QueryViewParams, url: &str) -> Result<Option<View>, WorkspaceError> {
-    let result = HttpRequestBuilder::get(&url.to_owned())
+    let result = request_builder()
+        .get(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -208,7 +246,8 @@ pub async fn read_view_request(token: &str, params: QueryViewParams, url: &str)
 }
 
 pub async fn update_view_request(token: &str, params: UpdateViewParams, url: &str) -> Result<(), WorkspaceError> {
-    let _ = HttpRequestBuilder::patch(&url.to_owned())
+    let _ = request_builder()
+        .patch(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()
@@ -217,7 +256,8 @@ pub async fn update_view_request(token: &str, params: UpdateViewParams, url: &st
 }
 
 pub async fn delete_view_request(token: &str, params: DeleteViewParams, url: &str) -> Result<(), WorkspaceError> {
-    let _ = HttpRequestBuilder::delete(&url.to_owned())
+    let _ = request_builder()
+        .delete(&url.to_owned())
         .header(HEADER_TOKEN, token)
         .protobuf(params)?
         .send()

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

@@ -263,6 +263,8 @@ impl WorkspaceController {
         spawn(async move {
             // Opti: retry?
             let workspaces = server.read_workspace(&token, params).await?;
+
+            // TODO: rollback if fail
             let _ = (&*conn).immediate_transaction::<_, WorkspaceError, _>(|| {
                 log::debug!("Save {} workspace", workspaces.len());
                 for workspace in &workspaces.items {
@@ -274,11 +276,17 @@ impl WorkspaceController {
                     log::debug!("Save {} apps", apps.len());
                     for mut app in apps {
                         let views = app.belongings.take_items();
-                        app_sql.create_app(AppTable::new(app), &*conn);
+                        match app_sql.create_app(AppTable::new(app), &*conn) {
+                            Ok(_) => {},
+                            Err(e) => log::error!("create app failed: {:?}", e),
+                        }
 
                         log::debug!("Save {} views", views.len());
                         for view in views {
-                            view_sql.create_view(ViewTable::new(view), &*conn);
+                            match view_sql.create_view(ViewTable::new(view), &*conn) {
+                                Ok(_) => {},
+                                Err(e) => log::error!("create view failed: {:?}", e),
+                            }
                         }
                     }
                 }