Jelajahi Sumber

[client]: fix restore & delete all bugs

appflowy 4 tahun lalu
induk
melakukan
3f83e7ee06
30 mengubah file dengan 321 tambahan dan 220 penghapusan
  1. 4 4
      app_flowy/lib/workspace/application/menu/menu_bloc.dart
  2. 3 3
      app_flowy/lib/workspace/application/menu/menu_user_bloc.dart
  3. 10 7
      app_flowy/lib/workspace/application/trash/trash_bloc.dart
  4. 5 5
      app_flowy/lib/workspace/application/view/view_bloc.dart
  5. 5 7
      app_flowy/lib/workspace/application/workspace/welcome_bloc.dart
  6. 1 1
      app_flowy/lib/workspace/infrastructure/deps_resolver.dart
  7. 35 36
      app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart
  8. 14 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pb.dart
  9. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbjson.dart
  10. 10 2
      backend/src/service/trash/router.rs
  11. 16 0
      backend/src/service/trash/trash.rs
  12. 2 2
      rust-lib/flowy-document/src/errors.rs
  13. 0 5
      rust-lib/flowy-document/src/module.rs
  14. 1 1
      rust-lib/flowy-document/src/services/cache.rs
  15. 1 29
      rust-lib/flowy-document/src/services/doc/doc_controller.rs
  16. 1 1
      rust-lib/flowy-document/src/services/doc/revision/rev_store.rs
  17. 0 2
      rust-lib/flowy-document/src/services/server/mod.rs
  18. 0 16
      rust-lib/flowy-document/src/services/server/server_api.rs
  19. 0 4
      rust-lib/flowy-document/src/services/server/server_api_mock.rs
  20. 1 1
      rust-lib/flowy-test/src/workspace.rs
  21. 18 17
      rust-lib/flowy-user/tests/event/auth_test.rs
  22. 7 4
      rust-lib/flowy-user/tests/event/user_profile_test.rs
  23. 22 2
      rust-lib/flowy-workspace/src/entities/trash/trash_create.rs
  24. 2 2
      rust-lib/flowy-workspace/src/handlers/trash_handler.rs
  25. 85 46
      rust-lib/flowy-workspace/src/protobuf/model/trash_create.rs
  26. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/trash_create.proto
  27. 45 5
      rust-lib/flowy-workspace/src/services/trash_can.rs
  28. 17 11
      rust-lib/flowy-workspace/src/services/view_controller.rs
  29. 5 0
      rust-lib/flowy-workspace/src/sql_tables/trash/trash_sql.rs
  30. 8 6
      rust-lib/flowy-workspace/tests/workspace/workspace_test.rs

+ 4 - 4
app_flowy/lib/workspace/application/menu/menu_bloc.dart

@@ -13,8 +13,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 part 'menu_bloc.freezed.dart';
 
 class MenuBloc extends Bloc<MenuEvent, MenuState> {
-  final IWorkspace workspace;
-  MenuBloc(this.workspace) : super(MenuState.initial());
+  final IWorkspace workspaceManager;
+  MenuBloc(this.workspaceManager) : super(MenuState.initial());
 
   @override
   Stream<MenuState> mapEventToState(
@@ -42,7 +42,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
   }
 
   Stream<MenuState> _performActionOnCreateApp(CreateApp event) async* {
-    final result = await workspace.createApp(name: event.name, desc: event.desc);
+    final result = await workspaceManager.createApp(name: event.name, desc: event.desc);
     yield result.fold(
       (app) => state.copyWith(apps: some([app])),
       (error) {
@@ -54,7 +54,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
 
   // ignore: unused_element
   Stream<MenuState> _fetchApps() async* {
-    final appsOrFail = await workspace.getApps();
+    final appsOrFail = await workspaceManager.getApps();
     yield appsOrFail.fold(
       (apps) => state.copyWith(apps: some(apps)),
       (error) {

+ 3 - 3
app_flowy/lib/workspace/application/menu/menu_user_bloc.dart

@@ -10,10 +10,10 @@ import 'package:dartz/dartz.dart';
 part 'menu_user_bloc.freezed.dart';
 
 class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
-  final IUser iUserImpl;
+  final IUser userManager;
   final IUserListener listener;
 
-  MenuUserBloc(this.iUserImpl, this.listener) : super(MenuUserState.initial(iUserImpl.user));
+  MenuUserBloc(this.userManager, this.listener) : super(MenuUserState.initial(userManager.user));
 
   @override
   Stream<MenuUserState> mapEventToState(MenuUserEvent event) async* {
@@ -36,7 +36,7 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
   }
 
   Future<void> _initUser() async {
-    final result = await iUserImpl.initUser();
+    final result = await userManager.initUser();
     result.fold((l) => null, (error) => Log.error(error));
   }
 

+ 10 - 7
app_flowy/lib/workspace/application/trash/trash_bloc.dart

@@ -8,16 +8,16 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 part 'trash_bloc.freezed.dart';
 
 class TrashBloc extends Bloc<TrashEvent, TrashState> {
-  final ITrash iTrash;
+  final ITrash trasnManager;
   final ITrashListener listener;
-  TrashBloc({required this.iTrash, required this.listener}) : super(TrashState.init());
+  TrashBloc({required this.trasnManager, required this.listener}) : super(TrashState.init());
 
   @override
   Stream<TrashState> mapEventToState(TrashEvent event) async* {
     yield* event.map(
       initial: (e) async* {
         listener.start(_listenTrashUpdated);
-        final result = await iTrash.readTrash();
+        final result = await trasnManager.readTrash();
         yield result.fold(
           (objects) => state.copyWith(objects: objects, successOrFailure: left(unit)),
           (error) => state.copyWith(successOrFailure: right(error)),
@@ -27,18 +27,21 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
         yield state.copyWith(objects: e.trash);
       },
       putback: (e) async* {
-        final result = await iTrash.putback(e.trashId);
+        final result = await trasnManager.putback(e.trashId);
         yield* _handleResult(result);
       },
       delete: (e) async* {
-        final result = await iTrash.deleteViews([e.trashId]);
+        final result = await trasnManager.deleteViews([e.trashId]);
         yield* _handleResult(result);
       },
       deleteAll: (e) async* {
-        final result = await iTrash.deleteAll();
+        final result = await trasnManager.deleteAll();
+        yield* _handleResult(result);
+      },
+      restoreAll: (e) async* {
+        final result = await trasnManager.restoreAll();
         yield* _handleResult(result);
       },
-      restoreAll: (e) async* {},
     );
   }
 

+ 5 - 5
app_flowy/lib/workspace/application/view/view_bloc.dart

@@ -8,13 +8,13 @@ import 'package:app_flowy/workspace/domain/i_view.dart';
 part 'view_bloc.freezed.dart';
 
 class ViewBloc extends Bloc<ViewEvent, ViewState> {
-  final IView iViewImpl;
+  final IView viewManager;
   final IViewListener listener;
 
   ViewBloc({
-    required this.iViewImpl,
+    required this.viewManager,
     required this.listener,
-  }) : super(ViewState.init(iViewImpl.view));
+  }) : super(ViewState.init(viewManager.view));
 
   @override
   Stream<ViewState> mapEventToState(ViewEvent event) async* {
@@ -26,13 +26,13 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
     }, viewDidUpdate: (e) async* {
       yield* _handleViewDidUpdate(e.result);
     }, rename: (e) async* {
-      final result = await iViewImpl.rename(e.newName);
+      final result = await viewManager.rename(e.newName);
       yield result.fold(
         (l) => state.copyWith(successOrFailure: left(unit)),
         (error) => state.copyWith(successOrFailure: right(error)),
       );
     }, delete: (e) async* {
-      final result = await iViewImpl.delete();
+      final result = await viewManager.delete();
       yield result.fold(
         (l) => state.copyWith(successOrFailure: left(unit)),
         (error) => state.copyWith(successOrFailure: right(error)),

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

@@ -11,16 +11,16 @@ part 'welcome_bloc.freezed.dart';
 
 class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
   final UserRepo repo;
-  final IUserListener watch;
-  WelcomeBloc({required this.repo, required this.watch}) : super(WelcomeState.initial());
+  final IUserListener listener;
+  WelcomeBloc({required this.repo, required this.listener}) : super(WelcomeState.initial());
 
   @override
   Stream<WelcomeState> mapEventToState(
     WelcomeEvent event,
   ) async* {
     yield* event.map(initial: (e) async* {
-      watch.setWorkspacesCallback(_workspacesUpdated);
-      watch.start();
+      listener.setWorkspacesCallback(_workspacesUpdated);
+      listener.start();
       //
       yield* _fetchWorkspaces();
     }, openWorkspace: (e) async* {
@@ -37,7 +37,7 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
 
   @override
   Future<void> close() async {
-    await watch.stop();
+    await listener.stop();
     super.close();
   }
 
@@ -67,7 +67,6 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
     final result = await repo.createWorkspace(name, desc);
     yield result.fold(
       (workspace) {
-        // add(const WelcomeEvent.fetchWorkspaces());
         return state.copyWith(successOrFailure: left(unit));
       },
       (error) {
@@ -88,7 +87,6 @@ class WelcomeEvent with _$WelcomeEvent {
   // const factory WelcomeEvent.fetchWorkspaces() = FetchWorkspace;
   const factory WelcomeEvent.createWorkspace(String name, String desc) = CreateWorkspace;
   const factory WelcomeEvent.openWorkspace(Workspace workspace) = OpenWorkspace;
-
   const factory WelcomeEvent.workspacesReveived(Either<List<Workspace>, WorkspaceError> workspacesOrFail) =
       WorkspacesReceived;
 }

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

@@ -34,7 +34,7 @@ class HomeDepsResolver {
     getIt.registerFactoryParam<WelcomeBloc, UserProfile, void>(
       (user, _) => WelcomeBloc(
         repo: UserRepo(user: user),
-        watch: getIt<IUserListener>(param1: user),
+        listener: getIt<IUserListener>(param1: user),
       ),
     );
 

+ 35 - 36
app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart

@@ -53,21 +53,27 @@ class _TrashStackPageState extends State<TrashStackPage> {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     const horizontalPadding = 80.0;
-    return SizedBox.expand(
-      child: Column(
-        children: [
-          _renderTopBar(theme),
-          const VSpace(32),
-          _renderTrashList(context),
-        ],
-        mainAxisAlignment: MainAxisAlignment.start,
-      ).padding(horizontal: horizontalPadding, vertical: 48),
+    return BlocProvider(
+      create: (context) => getIt<TrashBloc>()..add(const TrashEvent.initial()),
+      child: BlocBuilder<TrashBloc, TrashState>(
+        builder: (context, state) {
+          return SizedBox.expand(
+            child: Column(
+              children: [
+                _renderTopBar(context, theme, state),
+                const VSpace(32),
+                _renderTrashList(context, state),
+              ],
+              mainAxisAlignment: MainAxisAlignment.start,
+            ).padding(horizontal: horizontalPadding, vertical: 48),
+          );
+        },
+      ),
     );
   }
 
-  Widget _renderTrashList(BuildContext context) {
+  Widget _renderTrashList(BuildContext context, TrashState state) {
     const barSize = 6.0;
-
     return Expanded(
       child: ScrollbarListStack(
         axis: Axis.vertical,
@@ -86,8 +92,8 @@ class _TrashStackPageState extends State<TrashStackPage> {
                 physics: StyledScrollPhysics(),
                 controller: _scrollController,
                 slivers: [
-                  _renderListHeader(context),
-                  _renderListBody(context),
+                  _renderListHeader(context, state),
+                  _renderListBody(context, state),
                 ],
               ),
             ),
@@ -97,7 +103,7 @@ class _TrashStackPageState extends State<TrashStackPage> {
     );
   }
 
-  Widget _renderTopBar(AppTheme theme) {
+  Widget _renderTopBar(BuildContext context, AppTheme theme, TrashState state) {
     return SizedBox(
       height: 36,
       child: Row(
@@ -128,7 +134,7 @@ class _TrashStackPageState extends State<TrashStackPage> {
     );
   }
 
-  Widget _renderListHeader(BuildContext context) {
+  Widget _renderListHeader(BuildContext context, TrashState state) {
     return SliverPersistentHeader(
       delegate: TrashHeaderDelegate(),
       floating: true,
@@ -136,31 +142,24 @@ class _TrashStackPageState extends State<TrashStackPage> {
     );
   }
 
-  Widget _renderListBody(BuildContext context) {
-    return BlocProvider(
-      create: (context) => getIt<TrashBloc>()..add(const TrashEvent.initial()),
-      child: BlocBuilder<TrashBloc, TrashState>(
-        builder: (context, state) {
-          return SliverList(
-            delegate: SliverChildBuilderDelegate(
-              (BuildContext context, int index) {
-                final object = state.objects[index];
-                return SizedBox(
-                  height: 42,
-                  child: TrashCell(
-                    object: object,
-                    onRestore: () {
-                      context.read<TrashBloc>().add(TrashEvent.putback(object.id));
-                    },
-                    onDelete: () => context.read<TrashBloc>().add(TrashEvent.delete(object.id)),
-                  ),
-                );
+  Widget _renderListBody(BuildContext context, TrashState state) {
+    return SliverList(
+      delegate: SliverChildBuilderDelegate(
+        (BuildContext context, int index) {
+          final object = state.objects[index];
+          return SizedBox(
+            height: 42,
+            child: TrashCell(
+              object: object,
+              onRestore: () {
+                context.read<TrashBloc>().add(TrashEvent.putback(object.id));
               },
-              childCount: state.objects.length,
-              addAutomaticKeepAlives: false,
+              onDelete: () => context.read<TrashBloc>().add(TrashEvent.delete(object.id)),
             ),
           );
         },
+        childCount: state.objects.length,
+        addAutomaticKeepAlives: false,
       ),
     );
   }

+ 14 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pb.dart

@@ -17,17 +17,22 @@ export 'trash_create.pbenum.dart';
 class TrashIdentifiers extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TrashIdentifiers', createEmptyInstance: create)
     ..pc<TrashIdentifier>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: TrashIdentifier.create)
+    ..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deleteAll')
     ..hasRequiredFields = false
   ;
 
   TrashIdentifiers._() : super();
   factory TrashIdentifiers({
     $core.Iterable<TrashIdentifier>? items,
+    $core.bool? deleteAll,
   }) {
     final _result = create();
     if (items != null) {
       _result.items.addAll(items);
     }
+    if (deleteAll != null) {
+      _result.deleteAll = deleteAll;
+    }
     return _result;
   }
   factory TrashIdentifiers.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@@ -53,6 +58,15 @@ class TrashIdentifiers extends $pb.GeneratedMessage {
 
   @$pb.TagNumber(1)
   $core.List<TrashIdentifier> get items => $_getList(0);
+
+  @$pb.TagNumber(2)
+  $core.bool get deleteAll => $_getBF(1);
+  @$pb.TagNumber(2)
+  set deleteAll($core.bool v) { $_setBool(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasDeleteAll() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearDeleteAll() => clearField(2);
 }
 
 class TrashIdentifier extends $pb.GeneratedMessage {

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

@@ -24,11 +24,12 @@ const TrashIdentifiers$json = const {
   '1': 'TrashIdentifiers',
   '2': const [
     const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.TrashIdentifier', '10': 'items'},
+    const {'1': 'delete_all', '3': 2, '4': 1, '5': 8, '10': 'deleteAll'},
   ],
 };
 
 /// Descriptor for `TrashIdentifiers`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List trashIdentifiersDescriptor = $convert.base64Decode('ChBUcmFzaElkZW50aWZpZXJzEiYKBWl0ZW1zGAEgAygLMhAuVHJhc2hJZGVudGlmaWVyUgVpdGVtcw==');
+final $typed_data.Uint8List trashIdentifiersDescriptor = $convert.base64Decode('ChBUcmFzaElkZW50aWZpZXJzEiYKBWl0ZW1zGAEgAygLMhAuVHJhc2hJZGVudGlmaWVyUgVpdGVtcxIdCgpkZWxldGVfYWxsGAIgASgIUglkZWxldGVBbGw=');
 @$core.Deprecated('Use trashIdentifierDescriptor instead')
 const TrashIdentifier$json = const {
   '1': 'TrashIdentifier',

+ 10 - 2
backend/src/service/trash/router.rs

@@ -1,5 +1,5 @@
 use crate::service::{
-    trash::{create_trash, delete_trash, read_trash},
+    trash::{create_trash, delete_all_trash, delete_trash, read_trash},
     user::LoggedUser,
     util::parse_from_payload,
 };
@@ -42,6 +42,7 @@ pub async fn create_handler(
     Ok(FlowyResponse::success().into())
 }
 
+#[tracing::instrument(skip(payload, pool, logged_user), fields(delete_trash), err)]
 pub async fn delete_handler(
     payload: Payload,
     pool: Data<PgPool>,
@@ -53,7 +54,14 @@ pub async fn delete_handler(
         .await
         .context("Failed to acquire a Postgres connection to delete trash")?;
 
-    let _ = delete_trash(&mut transaction, make_records(params)?, &logged_user).await?;
+    if params.delete_all {
+        tracing::Span::current().record("delete_trash", &"all");
+        let _ = delete_all_trash(&mut transaction, &logged_user).await?;
+    } else {
+        let records = make_records(params)?;
+        let _ = delete_trash(&mut transaction, records, &logged_user).await?;
+    }
+
     transaction
         .commit()
         .await

+ 16 - 0
backend/src/service/trash/trash.rs

@@ -33,6 +33,22 @@ pub(crate) async fn create_trash(
     Ok(())
 }
 
+pub(crate) async fn delete_all_trash(
+    transaction: &mut DBTransaction<'_>,
+    user: &LoggedUser,
+) -> Result<(), ServerError> {
+    let (sql, args) = SqlBuilder::delete(TRASH_TABLE)
+        .and_where_eq("user_id", &user.user_id)
+        .build()?;
+    let result = sqlx::query_with(&sql, args)
+        .execute(transaction as &mut DBTransaction<'_>)
+        .await
+        .map_err(map_sqlx_error)?;
+
+    tracing::Span::current().record("affected_row", &result.rows_affected());
+    Ok(())
+}
+
 pub(crate) async fn delete_trash(
     transaction: &mut DBTransaction<'_>,
     records: Vec<(Uuid, i32)>,

+ 2 - 2
rust-lib/flowy-document/src/errors.rs

@@ -45,7 +45,7 @@ impl DocError {
 
     static_doc_error!(id_invalid, ErrorCode::DocIdInvalid);
     static_doc_error!(internal, ErrorCode::InternalError);
-    static_doc_error!(not_found, ErrorCode::DocNotfound);
+    static_doc_error!(record_not_found, ErrorCode::DocNotfound);
     static_doc_error!(unauthorized, ErrorCode::UserUnauthorized);
     static_doc_error!(ws, ErrorCode::WsConnectError);
     static_doc_error!(undo, ErrorCode::UndoFail);
@@ -97,7 +97,7 @@ impl std::default::Default for ErrorCode {
 impl std::convert::From<flowy_database::Error> for DocError {
     fn from(error: flowy_database::Error) -> Self {
         match error {
-            flowy_database::Error::NotFound => DocError::not_found().context(error),
+            flowy_database::Error::NotFound => DocError::record_not_found().context(error),
             _ => DocError::internal().context(error),
         }
     }

+ 0 - 5
rust-lib/flowy-document/src/module.rs

@@ -37,11 +37,6 @@ impl FlowyDocument {
         Ok(())
     }
 
-    pub fn create(&self, params: CreateDocParams) -> Result<(), DocError> {
-        let _ = self.doc_ctrl.create(params)?;
-        Ok(())
-    }
-
     pub fn delete(&self, params: DocIdentifier) -> Result<(), DocError> {
         let _ = self.doc_ctrl.delete(params)?;
         Ok(())

+ 1 - 1
rust-lib/flowy-document/src/services/cache.rs

@@ -46,4 +46,4 @@ impl DocCache {
     }
 }
 
-fn doc_not_found() -> DocError { DocError::not_found().context("Doc is close or you should call open first") }
+fn doc_not_found() -> DocError { DocError::record_not_found().context("Doc is close or you should call open first") }

+ 1 - 29
rust-lib/flowy-document/src/services/doc/doc_controller.rs

@@ -45,17 +45,6 @@ impl DocController {
         Ok(())
     }
 
-    #[tracing::instrument(skip(self), err)]
-    pub(crate) fn create(&self, params: CreateDocParams) -> Result<(), DocError> {
-        // let _doc = Doc {
-        //     id: params.id,
-        //     data: params.data,
-        //     rev_id: 0,
-        // };
-        // let _ = self.doc_sql.create_doc_table(DocTable::new(doc), conn)?;
-        Ok(())
-    }
-
     #[tracing::instrument(level = "debug", skip(self, pool), err)]
     pub(crate) async fn open(
         &self,
@@ -82,7 +71,6 @@ impl DocController {
         let doc_id = &params.doc_id;
         self.cache.remove(doc_id);
         self.ws_manager.remove_handler(doc_id);
-        let _ = self.delete_doc_on_server(params)?;
         Ok(())
     }
 
@@ -95,22 +83,6 @@ impl DocController {
 }
 
 impl DocController {
-    #[tracing::instrument(level = "debug", skip(self), err)]
-    fn delete_doc_on_server(&self, params: DocIdentifier) -> Result<(), DocError> {
-        let token = self.user.token()?;
-        let server = self.server.clone();
-        tokio::spawn(async move {
-            match server.delete_doc(&token, params).await {
-                Ok(_) => {},
-                Err(e) => {
-                    // TODO: retry?
-                    log::error!("Delete doc failed: {:?}", e);
-                },
-            }
-        });
-        Ok(())
-    }
-
     async fn make_edit_context(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<Arc<ClientEditDoc>, DocError> {
         // Opti: require upgradable_read lock and then upgrade to write lock using
         // RwLockUpgradableReadGuard::upgrade(xx) of ws
@@ -146,7 +118,7 @@ impl RevisionServer for RevisionServerImpl {
 
         ResultFuture::new(async move {
             match server.read_doc(&token, params).await? {
-                None => Err(DocError::not_found()),
+                None => Err(DocError::record_not_found().context("Remote doesn't have this document")),
                 Some(doc) => Ok(doc),
             }
         })

+ 1 - 1
rust-lib/flowy-document/src/services/doc/revision/rev_store.rs

@@ -180,7 +180,7 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
         let conn = &*persistence.pool.get().map_err(internal_error)?;
         let revisions = persistence.rev_sql.read_rev_tables(&doc_id, None, conn)?;
         if revisions.is_empty() {
-            return Err(DocError::not_found());
+            return Err(DocError::record_not_found().context("Local doesn't have this document"));
         }
 
         let base_rev_id: RevId = revisions.last().unwrap().base_rev_id.into();

+ 0 - 2
rust-lib/flowy-document/src/services/server/mod.rs

@@ -20,8 +20,6 @@ pub trait DocumentServerAPI {
     fn read_doc(&self, token: &str, params: DocIdentifier) -> ResultFuture<Option<Doc>, DocError>;
 
     fn update_doc(&self, token: &str, params: UpdateDocParams) -> ResultFuture<(), DocError>;
-
-    fn delete_doc(&self, token: &str, params: DocIdentifier) -> ResultFuture<(), DocError>;
 }
 
 pub(crate) fn construct_doc_server(server_config: &ServerConfig) -> Arc<dyn DocumentServerAPI + Send + Sync> {

+ 0 - 16
rust-lib/flowy-document/src/services/server/server_api.rs

@@ -32,12 +32,6 @@ impl DocumentServerAPI for DocServer {
         let url = self.config.doc_url();
         ResultFuture::new(async move { update_doc_request(&token, params, &url).await })
     }
-
-    fn delete_doc(&self, token: &str, params: DocIdentifier) -> ResultFuture<(), DocError> {
-        let token = token.to_owned();
-        let url = self.config.doc_url();
-        ResultFuture::new(async move { delete_doc_request(&token, params, &url).await })
-    }
 }
 
 pub(crate) fn request_builder() -> HttpRequestBuilder {
@@ -74,13 +68,3 @@ pub async fn update_doc_request(token: &str, params: UpdateDocParams, url: &str)
         .await?;
     Ok(())
 }
-
-pub async fn delete_doc_request(token: &str, params: DocIdentifier, url: &str) -> Result<(), DocError> {
-    let _ = request_builder()
-        .delete(url)
-        .header(HEADER_TOKEN, token)
-        .protobuf(params)?
-        .send()
-        .await?;
-    Ok(())
-}

+ 0 - 4
rust-lib/flowy-document/src/services/server/server_api_mock.rs

@@ -18,8 +18,4 @@ impl DocumentServerAPI for DocServerMock {
     fn update_doc(&self, _token: &str, _params: UpdateDocParams) -> ResultFuture<(), DocError> {
         ResultFuture::new(async { Ok(()) })
     }
-
-    fn delete_doc(&self, _token: &str, _params: DocIdentifier) -> ResultFuture<(), DocError> {
-        ResultFuture::new(async { Ok(()) })
-    }
 }

+ 1 - 1
rust-lib/flowy-test/src/workspace.rs

@@ -133,7 +133,7 @@ pub fn read_workspace(sdk: &FlowyTestSDK, request: QueryWorkspaceRequest) -> Opt
     let mut workspaces;
     if let Some(workspace_id) = &request.workspace_id {
         workspaces = repeated_workspace
-            .take_items()
+            .into_inner()
             .into_iter()
             .filter(|workspace| &workspace.id == workspace_id)
             .collect::<Vec<Workspace>>();

+ 18 - 17
rust-lib/flowy-user/tests/event/auth_test.rs

@@ -3,9 +3,8 @@ use flowy_test::{builder::UserTest, FlowyTest};
 use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
 use serial_test::*;
 
-#[test]
-#[serial]
-fn sign_up_with_invalid_email() {
+#[tokio::test]
+async fn sign_up_with_invalid_email() {
     for email in invalid_email_test_case() {
         let test = FlowyTest::setup();
         let request = SignUpRequest {
@@ -18,16 +17,16 @@ fn sign_up_with_invalid_email() {
             UserTest::new(test.sdk)
                 .event(SignUp)
                 .request(request)
-                .sync_send()
+                .async_send()
+                .await
                 .error()
                 .code,
             ErrorCode::EmailFormatInvalid
         );
     }
 }
-#[test]
-#[serial]
-fn sign_up_with_invalid_password() {
+#[tokio::test]
+async fn sign_up_with_invalid_password() {
     for password in invalid_password_test_case() {
         let test = FlowyTest::setup();
         let request = SignUpRequest {
@@ -39,7 +38,8 @@ fn sign_up_with_invalid_password() {
         UserTest::new(test.sdk)
             .event(SignUp)
             .request(request)
-            .sync_send()
+            .async_send()
+            .await
             .assert_error();
     }
 }
@@ -58,14 +58,14 @@ async fn sign_in_success() {
     let response = UserTest::new(test.sdk())
         .event(SignIn)
         .request(request)
-        .sync_send()
+        .async_send()
+        .await
         .parse::<UserProfile>();
     dbg!(&response);
 }
 
-#[test]
-#[serial]
-fn sign_in_with_invalid_email() {
+#[tokio::test]
+async fn sign_in_with_invalid_email() {
     for email in invalid_email_test_case() {
         let test = FlowyTest::setup();
         let request = SignInRequest {
@@ -77,7 +77,8 @@ fn sign_in_with_invalid_email() {
             UserTest::new(test.sdk)
                 .event(SignIn)
                 .request(request)
-                .sync_send()
+                .async_send()
+                .await
                 .error()
                 .code,
             ErrorCode::EmailFormatInvalid
@@ -85,9 +86,8 @@ fn sign_in_with_invalid_email() {
     }
 }
 
-#[test]
-#[serial]
-fn sign_in_with_invalid_password() {
+#[tokio::test]
+async fn sign_in_with_invalid_password() {
     for password in invalid_password_test_case() {
         let test = FlowyTest::setup();
 
@@ -99,7 +99,8 @@ fn sign_in_with_invalid_password() {
         UserTest::new(test.sdk)
             .event(SignIn)
             .request(request)
-            .sync_send()
+            .async_send()
+            .await
             .assert_error();
     }
 }

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

@@ -4,11 +4,14 @@ use flowy_test::{builder::UserTest, FlowyTest};
 use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
 use serial_test::*;
 
-#[test]
-#[serial]
-fn user_profile_get_failed() {
+#[tokio::test]
+async fn user_profile_get_failed() {
     let test = FlowyTest::setup();
-    let result = UserTest::new(test.sdk).event(GetUserProfile).assert_error().sync_send();
+    let result = UserTest::new(test.sdk)
+        .event(GetUserProfile)
+        .assert_error()
+        .async_send()
+        .await;
     assert!(result.user_profile().is_none())
 }
 

+ 22 - 2
rust-lib/flowy-workspace/src/entities/trash/trash_create.rs

@@ -27,10 +27,27 @@ impl std::default::Default for TrashType {
 pub struct TrashIdentifiers {
     #[pb(index = 1)]
     pub items: Vec<TrashIdentifier>,
+
+    #[pb(index = 2)]
+    pub delete_all: bool,
+}
+
+impl TrashIdentifiers {
+    pub fn all() -> TrashIdentifiers {
+        TrashIdentifiers {
+            items: vec![],
+            delete_all: true,
+        }
+    }
 }
 
 impl std::convert::From<Vec<TrashIdentifier>> for TrashIdentifiers {
-    fn from(items: Vec<TrashIdentifier>) -> Self { TrashIdentifiers { items } }
+    fn from(items: Vec<TrashIdentifier>) -> Self {
+        TrashIdentifiers {
+            items,
+            delete_all: false,
+        }
+    }
 }
 
 impl std::convert::From<Vec<Trash>> for TrashIdentifiers {
@@ -40,7 +57,10 @@ impl std::convert::From<Vec<Trash>> for TrashIdentifiers {
             .map(|t| TrashIdentifier { id: t.id, ty: t.ty })
             .collect::<Vec<_>>();
 
-        TrashIdentifiers { items }
+        TrashIdentifiers {
+            items,
+            delete_all: false,
+        }
     }
 }
 

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

@@ -33,12 +33,12 @@ pub(crate) async fn delete_trash_handler(
 
 #[tracing::instrument(skip(controller), err)]
 pub(crate) async fn restore_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> {
-    let _ = controller.restore_all()?;
+    let _ = controller.restore_all().await?;
     Ok(())
 }
 
 #[tracing::instrument(skip(controller), err)]
 pub(crate) async fn delete_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> {
-    let _ = controller.delete_all()?;
+    let _ = controller.delete_all().await?;
     Ok(())
 }

+ 85 - 46
rust-lib/flowy-workspace/src/protobuf/model/trash_create.rs

@@ -27,6 +27,7 @@
 pub struct TrashIdentifiers {
     // message fields
     pub items: ::protobuf::RepeatedField<TrashIdentifier>,
+    pub delete_all: bool,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -67,6 +68,21 @@ impl TrashIdentifiers {
     pub fn take_items(&mut self) -> ::protobuf::RepeatedField<TrashIdentifier> {
         ::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new())
     }
+
+    // bool delete_all = 2;
+
+
+    pub fn get_delete_all(&self) -> bool {
+        self.delete_all
+    }
+    pub fn clear_delete_all(&mut self) {
+        self.delete_all = false;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_delete_all(&mut self, v: bool) {
+        self.delete_all = v;
+    }
 }
 
 impl ::protobuf::Message for TrashIdentifiers {
@@ -86,6 +102,13 @@ impl ::protobuf::Message for TrashIdentifiers {
                 1 => {
                     ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?;
                 },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_bool()?;
+                    self.delete_all = tmp;
+                },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
                 },
@@ -102,6 +125,9 @@ impl ::protobuf::Message for TrashIdentifiers {
             let len = value.compute_size();
             my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
         };
+        if self.delete_all != false {
+            my_size += 2;
+        }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
         my_size
@@ -113,6 +139,9 @@ impl ::protobuf::Message for TrashIdentifiers {
             os.write_raw_varint32(v.get_cached_size())?;
             v.write_to_with_cached_sizes(os)?;
         };
+        if self.delete_all != false {
+            os.write_bool(2, self.delete_all)?;
+        }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
     }
@@ -156,6 +185,11 @@ impl ::protobuf::Message for TrashIdentifiers {
                 |m: &TrashIdentifiers| { &m.items },
                 |m: &mut TrashIdentifiers| { &mut m.items },
             ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
+                "delete_all",
+                |m: &TrashIdentifiers| { &m.delete_all },
+                |m: &mut TrashIdentifiers| { &mut m.delete_all },
+            ));
             ::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifiers>(
                 "TrashIdentifiers",
                 fields,
@@ -173,6 +207,7 @@ impl ::protobuf::Message for TrashIdentifiers {
 impl ::protobuf::Clear for TrashIdentifiers {
     fn clear(&mut self) {
         self.items.clear();
+        self.delete_all = false;
         self.unknown_fields.clear();
     }
 }
@@ -898,52 +933,56 @@ impl ::protobuf::reflect::ProtobufValue for TrashType {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x12trash_create.proto\":\n\x10TrashIdentifiers\x12&\n\x05items\x18\
-    \x01\x20\x03(\x0b2\x10.TrashIdentifierR\x05items\"=\n\x0fTrashIdentifier\
-    \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x1a\n\x02ty\x18\x02\x20\
-    \x01(\x0e2\n.TrashTypeR\x02ty\"\x8d\x01\n\x05Trash\x12\x0e\n\x02id\x18\
-    \x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\
-    #\n\rmodified_time\x18\x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcr\
-    eate_time\x18\x04\x20\x01(\x03R\ncreateTime\x12\x1a\n\x02ty\x18\x05\x20\
-    \x01(\x0e2\n.TrashTypeR\x02ty\"-\n\rRepeatedTrash\x12\x1c\n\x05items\x18\
-    \x01\x20\x03(\x0b2\x06.TrashR\x05items*\"\n\tTrashType\x12\x0b\n\x07Unkn\
-    own\x10\0\x12\x08\n\x04View\x10\x01J\xe7\x05\n\x06\x12\x04\0\0\x16\x01\n\
-    \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\
-    \n\x03\x04\0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
-    \x04'\n\x0c\n\x05\x04\0\x02\0\x04\x12\x03\x03\x04\x0c\n\x0c\n\x05\x04\0\
-    \x02\0\x06\x12\x03\x03\r\x1c\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x1d\
-    \"\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03%&\n\n\n\x02\x04\x01\x12\x04\
-    \x05\0\x08\x01\n\n\n\x03\x04\x01\x01\x12\x03\x05\x08\x17\n\x0b\n\x04\x04\
-    \x01\x02\0\x12\x03\x06\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x06\
-    \x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x06\x0b\r\n\x0c\n\x05\x04\
-    \x01\x02\0\x03\x12\x03\x06\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\
-    \x07\x04\x15\n\x0c\n\x05\x04\x01\x02\x01\x06\x12\x03\x07\x04\r\n\x0c\n\
-    \x05\x04\x01\x02\x01\x01\x12\x03\x07\x0e\x10\n\x0c\n\x05\x04\x01\x02\x01\
-    \x03\x12\x03\x07\x13\x14\n\n\n\x02\x04\x02\x12\x04\t\0\x0f\x01\n\n\n\x03\
-    \x04\x02\x01\x12\x03\t\x08\r\n\x0b\n\x04\x04\x02\x02\0\x12\x03\n\x04\x12\
-    \n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x02\x02\0\
-    \x01\x12\x03\n\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\n\x10\x11\n\
-    \x0b\n\x04\x04\x02\x02\x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x04\x02\x02\
-    \x01\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0b\
-    \x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0b\x12\x13\n\x0b\n\x04\
-    \x04\x02\x02\x02\x12\x03\x0c\x04\x1c\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\
-    \x03\x0c\x04\t\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x0c\n\x17\n\x0c\n\
-    \x05\x04\x02\x02\x02\x03\x12\x03\x0c\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x03\
-    \x12\x03\r\x04\x1a\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\r\x04\t\n\x0c\
-    \n\x05\x04\x02\x02\x03\x01\x12\x03\r\n\x15\n\x0c\n\x05\x04\x02\x02\x03\
-    \x03\x12\x03\r\x18\x19\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x0e\x04\x15\n\
-    \x0c\n\x05\x04\x02\x02\x04\x06\x12\x03\x0e\x04\r\n\x0c\n\x05\x04\x02\x02\
-    \x04\x01\x12\x03\x0e\x0e\x10\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x0e\
-    \x13\x14\n\n\n\x02\x04\x03\x12\x04\x10\0\x12\x01\n\n\n\x03\x04\x03\x01\
-    \x12\x03\x10\x08\x15\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x11\x04\x1d\n\x0c\
-    \n\x05\x04\x03\x02\0\x04\x12\x03\x11\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\
-    \x06\x12\x03\x11\r\x12\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x11\x13\x18\
-    \n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x11\x1b\x1c\n\n\n\x02\x05\0\x12\
-    \x04\x13\0\x16\x01\n\n\n\x03\x05\0\x01\x12\x03\x13\x05\x0e\n\x0b\n\x04\
-    \x05\0\x02\0\x12\x03\x14\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x14\
-    \x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x14\x0e\x0f\n\x0b\n\x04\x05\
-    \0\x02\x01\x12\x03\x15\x04\r\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x15\
-    \x04\x08\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x15\x0b\x0cb\x06proto3\
+    \n\x12trash_create.proto\"Y\n\x10TrashIdentifiers\x12&\n\x05items\x18\
+    \x01\x20\x03(\x0b2\x10.TrashIdentifierR\x05items\x12\x1d\n\ndelete_all\
+    \x18\x02\x20\x01(\x08R\tdeleteAll\"=\n\x0fTrashIdentifier\x12\x0e\n\x02i\
+    d\x18\x01\x20\x01(\tR\x02id\x12\x1a\n\x02ty\x18\x02\x20\x01(\x0e2\n.Tras\
+    hTypeR\x02ty\"\x8d\x01\n\x05Trash\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
+    \x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12#\n\rmodified_ti\
+    me\x18\x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcreate_time\x18\
+    \x04\x20\x01(\x03R\ncreateTime\x12\x1a\n\x02ty\x18\x05\x20\x01(\x0e2\n.T\
+    rashTypeR\x02ty\"-\n\rRepeatedTrash\x12\x1c\n\x05items\x18\x01\x20\x03(\
+    \x0b2\x06.TrashR\x05items*\"\n\tTrashType\x12\x0b\n\x07Unknown\x10\0\x12\
+    \x08\n\x04View\x10\x01J\x9e\x06\n\x06\x12\x04\0\0\x17\x01\n\x08\n\x01\
+    \x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\
+    \0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04'\n\
+    \x0c\n\x05\x04\0\x02\0\x04\x12\x03\x03\x04\x0c\n\x0c\n\x05\x04\0\x02\0\
+    \x06\x12\x03\x03\r\x1c\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x1d\"\n\
+    \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03%&\n\x0b\n\x04\x04\0\x02\x01\x12\
+    \x03\x04\x04\x18\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\x08\n\x0c\
+    \n\x05\x04\0\x02\x01\x01\x12\x03\x04\t\x13\n\x0c\n\x05\x04\0\x02\x01\x03\
+    \x12\x03\x04\x16\x17\n\n\n\x02\x04\x01\x12\x04\x06\0\t\x01\n\n\n\x03\x04\
+    \x01\x01\x12\x03\x06\x08\x17\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x07\x04\
+    \x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\x01\
+    \x02\0\x01\x12\x03\x07\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x07\
+    \x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x08\x04\x15\n\x0c\n\x05\x04\
+    \x01\x02\x01\x06\x12\x03\x08\x04\r\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\
+    \x03\x08\x0e\x10\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x08\x13\x14\n\n\
+    \n\x02\x04\x02\x12\x04\n\0\x10\x01\n\n\n\x03\x04\x02\x01\x12\x03\n\x08\r\
+    \n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0b\x04\x12\n\x0c\n\x05\x04\x02\x02\0\
+    \x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0b\x0b\r\n\
+    \x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0b\x10\x11\n\x0b\n\x04\x04\x02\x02\
+    \x01\x12\x03\x0c\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x0c\x04\
+    \n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0c\x0b\x0f\n\x0c\n\x05\x04\
+    \x02\x02\x01\x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\
+    \r\x04\x1c\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\r\x04\t\n\x0c\n\x05\
+    \x04\x02\x02\x02\x01\x12\x03\r\n\x17\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\
+    \x03\r\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x0e\x04\x1a\n\x0c\n\
+    \x05\x04\x02\x02\x03\x05\x12\x03\x0e\x04\t\n\x0c\n\x05\x04\x02\x02\x03\
+    \x01\x12\x03\x0e\n\x15\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x0e\x18\
+    \x19\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x0f\x04\x15\n\x0c\n\x05\x04\x02\
+    \x02\x04\x06\x12\x03\x0f\x04\r\n\x0c\n\x05\x04\x02\x02\x04\x01\x12\x03\
+    \x0f\x0e\x10\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x0f\x13\x14\n\n\n\
+    \x02\x04\x03\x12\x04\x11\0\x13\x01\n\n\n\x03\x04\x03\x01\x12\x03\x11\x08\
+    \x15\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x12\x04\x1d\n\x0c\n\x05\x04\x03\
+    \x02\0\x04\x12\x03\x12\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\x06\x12\x03\x12\
+    \r\x12\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x12\x13\x18\n\x0c\n\x05\x04\
+    \x03\x02\0\x03\x12\x03\x12\x1b\x1c\n\n\n\x02\x05\0\x12\x04\x14\0\x17\x01\
+    \n\n\n\x03\x05\0\x01\x12\x03\x14\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\
+    \x15\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x15\x04\x0b\n\x0c\n\x05\
+    \x05\0\x02\0\x02\x12\x03\x15\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\
+    \x16\x04\r\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x16\x04\x08\n\x0c\n\x05\
+    \x05\0\x02\x01\x02\x12\x03\x16\x0b\x0cb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

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

@@ -2,6 +2,7 @@ syntax = "proto3";
 
 message TrashIdentifiers {
     repeated TrashIdentifier items = 1;
+    bool delete_all = 2;
 }
 message TrashIdentifier {
     string id = 1;

+ 45 - 5
rust-lib/flowy-workspace/src/services/trash_can.rs

@@ -55,11 +55,12 @@ impl TrashCan {
         let trash_table = TrashTableSql::read(trash_id, &*self.database.db_connection()?)?;
         let _ = thread::scope(|_s| {
             let conn = self.database.db_connection()?;
-            let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+            conn.immediate_transaction::<_, WorkspaceError, _>(|| {
                 let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
                 notify_trash_num_changed(TrashTableSql::read_all(&conn)?);
                 Ok(())
             })?;
+
             Ok::<(), WorkspaceError>(())
         })
         .unwrap()?;
@@ -71,6 +72,7 @@ impl TrashCan {
 
         let _ = self.delete_trash_on_server(TrashIdentifiers {
             items: vec![identifier.clone()],
+            delete_all: false,
         })?;
 
         tracing::Span::current().record("putback", &format!("{:?}", &identifier).as_str());
@@ -80,16 +82,45 @@ impl TrashCan {
     }
 
     #[tracing::instrument(level = "debug", skip(self)  err)]
-    pub fn restore_all(&self) -> WorkspaceResult<()> { Ok(()) }
+    pub async fn restore_all(&self) -> WorkspaceResult<()> {
+        let repeated_trash = self.delete_all_trash_on_local()?;
+        let identifiers: TrashIdentifiers = repeated_trash.items.clone().into();
+        let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
+        let _ = self.notify.send(TrashEvent::Putback(identifiers, tx));
+        let _ = rx.recv().await;
+
+        notify_trash_num_changed(RepeatedTrash { items: vec![] });
+        let _ = self.delete_all_trash_on_server().await?;
+        Ok(())
+    }
 
     #[tracing::instrument(level = "debug", skip(self)  err)]
-    pub fn delete_all(&self) -> WorkspaceResult<()> { Ok(()) }
+    pub async fn delete_all(&self) -> WorkspaceResult<()> {
+        let repeated_trash = self.delete_all_trash_on_local()?;
+        let identifiers: TrashIdentifiers = repeated_trash.items.clone().into();
+        let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
+        let _ = self.notify.send(TrashEvent::Delete(identifiers, tx));
+        let _ = rx.recv().await;
+
+        notify_trash_num_changed(RepeatedTrash { items: vec![] });
+        let _ = self.delete_all_trash_on_server().await?;
+        Ok(())
+    }
+
+    fn delete_all_trash_on_local(&self) -> WorkspaceResult<RepeatedTrash> {
+        let conn = self.database.db_connection()?;
+        conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+            let repeated_trash = TrashTableSql::read_all(&*conn)?;
+            let _ = TrashTableSql::delete_all(&*conn)?;
+            Ok(repeated_trash)
+        })
+    }
 
     #[tracing::instrument(level = "debug", skip(self)  err)]
     pub async fn delete(&self, trash_identifiers: TrashIdentifiers) -> WorkspaceResult<()> {
         let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
         let _ = self.notify.send(TrashEvent::Delete(trash_identifiers.clone(), tx));
-        let _ = rx.recv().await.unwrap()?;
+        let _ = rx.recv().await;
 
         let conn = self.database.db_connection()?;
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
@@ -99,6 +130,7 @@ impl TrashCan {
             Ok(())
         })?;
 
+        notify_trash_num_changed(TrashTableSql::read_all(&conn)?);
         let _ = self.delete_trash_on_server(trash_identifiers)?;
 
         Ok(())
@@ -122,7 +154,8 @@ impl TrashCan {
             let conn = self.database.db_connection()?;
             conn.immediate_transaction::<_, WorkspaceError, _>(|| {
                 let _ = TrashTableSql::create_trash(repeated_trash.clone(), &*conn)?;
-                self.create_trash_on_server(repeated_trash);
+                let _ = self.create_trash_on_server(repeated_trash);
+
                 notify_trash_num_changed(TrashTableSql::read_all(&conn)?);
                 Ok(())
             })?;
@@ -202,6 +235,13 @@ impl TrashCan {
         });
         Ok(())
     }
+
+    #[tracing::instrument(level = "debug", skip(self), err)]
+    async fn delete_all_trash_on_server(&self) -> WorkspaceResult<()> {
+        let token = self.user.token()?;
+        let server = self.server.clone();
+        server.delete_trash(&token, TrashIdentifiers::all()).await
+    }
 }
 
 #[tracing::instrument(skip(repeated_trash), fields(trash_count))]

+ 17 - 11
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -23,7 +23,7 @@ use flowy_document::{
 use crate::{entities::trash::TrashType, errors::WorkspaceResult};
 
 use futures::{FutureExt, StreamExt};
-use std::sync::Arc;
+use std::{collections::HashSet, sync::Arc};
 
 pub(crate) struct ViewController {
     user: Arc<dyn WorkspaceUser>,
@@ -61,10 +61,9 @@ impl ViewController {
         let view = self.create_view_on_server(params.clone()).await?;
         let conn = &*self.database.db_connection()?;
         let trash_can = self.trash_can.clone();
-        // TODO: rollback anything created before if failed?
+
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let _ = self.save_view(view.clone(), conn)?;
-            self.document.create(CreateDocParams::new(&view.id, params.data))?;
             let repeated_view = read_belonging_view(&view.belong_to_id, trash_can, &conn)?;
             send_dart_notification(&view.belong_to_id, WorkspaceNotification::AppViewsChanged)
                 .payload(repeated_view)
@@ -241,7 +240,9 @@ async fn handle_trash_event(
                 let conn = &*db_result?;
                 let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
                     for identifier in identifiers.items {
-                        let _ = notify_view_num_changed(&identifier.id, conn, trash_can.clone())?;
+                        let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
+                        let repeated_view = read_belonging_view(&view_table.belong_to_id, trash_can.clone(), conn)?;
+                        let _ = notify_view_num_changed(&view_table.belong_to_id, repeated_view)?;
                     }
                     Ok(())
                 })?;
@@ -253,11 +254,19 @@ async fn handle_trash_event(
             let result = || {
                 let conn = &*db_result?;
                 let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+                    let mut notify_ids = HashSet::new();
                     for identifier in identifiers.items {
+                        let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
                         let _ = ViewTableSql::delete_view(&identifier.id, conn)?;
                         let _ = document.delete(identifier.id.clone().into())?;
-                        let _ = notify_view_num_changed(&identifier.id, conn, trash_can.clone())?;
+                        notify_ids.insert(view_table.belong_to_id);
+                    }
+
+                    for notify_id in notify_ids {
+                        let repeated_view = read_belonging_view(&notify_id, trash_can.clone(), conn)?;
+                        let _ = notify_view_num_changed(&notify_id, repeated_view)?;
                     }
+
                     Ok(())
                 })?;
                 Ok::<(), WorkspaceError>(())
@@ -267,12 +276,9 @@ async fn handle_trash_event(
     }
 }
 
-#[tracing::instrument(skip(conn, trash_can), err)]
-fn notify_view_num_changed(view_id: &str, conn: &SqliteConnection, trash_can: Arc<TrashCan>) -> WorkspaceResult<()> {
-    let view_table = ViewTableSql::read_view(view_id, conn)?;
-    let repeated_view = read_belonging_view(&view_table.belong_to_id, trash_can, conn)?;
-
-    send_dart_notification(&view_table.belong_to_id, WorkspaceNotification::AppViewsChanged)
+#[tracing::instrument(skip(repeated_view), err)]
+fn notify_view_num_changed(belong_to_id: &str, repeated_view: RepeatedView) -> WorkspaceResult<()> {
+    send_dart_notification(&belong_to_id, WorkspaceNotification::AppViewsChanged)
         .payload(repeated_view)
         .send();
     Ok(())

+ 5 - 0
rust-lib/flowy-workspace/src/sql_tables/trash/trash_sql.rs

@@ -35,6 +35,11 @@ impl TrashTableSql {
         Ok(RepeatedTrash { items })
     }
 
+    pub(crate) fn delete_all(conn: &SqliteConnection) -> Result<(), WorkspaceError> {
+        let _ = diesel::delete(dsl::trash_table).execute(conn)?;
+        Ok(())
+    }
+
     pub(crate) fn read(trash_id: &str, conn: &SqliteConnection) -> Result<TrashTable, WorkspaceError> {
         let trash_table = dsl::trash_table
             .filter(trash_table::id.eq(trash_id))

+ 8 - 6
rust-lib/flowy-workspace/tests/workspace/workspace_test.rs

@@ -29,8 +29,8 @@ async fn workspace_create_with_apps() {
     assert_eq!(&app, workspace_from_db.apps.first_or_crash());
 }
 
-#[test]
-fn workspace_create_with_invalid_name() {
+#[tokio::test]
+async fn workspace_create_with_invalid_name() {
     for name in invalid_workspace_name_test_case() {
         let sdk = FlowyTest::setup().sdk;
         let request = CreateWorkspaceRequest {
@@ -41,7 +41,8 @@ fn workspace_create_with_invalid_name() {
             FlowyWorkspaceTest::new(sdk)
                 .event(CreateWorkspace)
                 .request(request)
-                .sync_send()
+                .async_send()
+                .await
                 .error()
                 .code,
             ErrorCode::WorkspaceNameInvalid
@@ -49,8 +50,8 @@ fn workspace_create_with_invalid_name() {
     }
 }
 
-#[test]
-fn workspace_update_with_invalid_name() {
+#[tokio::test]
+async fn workspace_update_with_invalid_name() {
     let sdk = FlowyTest::setup().sdk;
     for name in invalid_workspace_name_test_case() {
         let request = CreateWorkspaceRequest {
@@ -61,7 +62,8 @@ fn workspace_update_with_invalid_name() {
             FlowyWorkspaceTest::new(sdk.clone())
                 .event(CreateWorkspace)
                 .request(request)
-                .sync_send()
+                .async_send()
+                .await
                 .error()
                 .code,
             ErrorCode::WorkspaceNameInvalid