瀏覽代碼

create view with doc, ignore thumbnail for now

appflowy 3 年之前
父節點
當前提交
9ade419b22
共有 46 個文件被更改,包括 295 次插入412 次删除
  1. 5 1
      app_flowy/lib/workspace/application/app/app_bloc.dart
  2. 6 4
      app_flowy/lib/workspace/application/view/doc_watch_bloc.dart
  3. 28 28
      app_flowy/lib/workspace/application/view/doc_watch_bloc.freezed.dart
  4. 3 3
      app_flowy/lib/workspace/application/view/view_list_bloc.dart
  5. 21 23
      app_flowy/lib/workspace/application/view/view_list_bloc.freezed.dart
  6. 8 9
      app_flowy/lib/workspace/domain/i_doc.dart
  7. 1 17
      app_flowy/lib/workspace/infrastructure/i_app_impl.dart
  8. 10 23
      app_flowy/lib/workspace/infrastructure/i_doc_impl.dart
  9. 13 28
      app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart
  10. 2 0
      app_flowy/lib/workspace/presentation/app/view_list_page.dart
  11. 3 3
      app_flowy/lib/workspace/presentation/doc/editor_page.dart
  12. 1 0
      app_flowy/lib/workspace/presentation/home/home_screen.dart
  13. 1 1
      app_flowy/packages/flowy_infra/lib/flowy_logger.dart
  14. 13 47
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  15. 1 1
      backend/src/service/doc_service/router.rs
  16. 14 12
      backend/src/service/workspace_service/user_default/user_default.rs
  17. 20 14
      backend/src/service/workspace_service/view/view.rs
  18. 1 1
      backend/tests/api/helper.rs
  19. 1 1
      backend/tests/api/workspace.rs
  20. 1 1
      rust-lib/flowy-database/src/lib.rs
  21. 2 4
      rust-lib/flowy-dispatch/tests/api/helper.rs
  22. 1 1
      rust-lib/flowy-document/Flowy.toml
  23. 0 4
      rust-lib/flowy-document/src/entities/doc/doc.rs
  24. 17 1
      rust-lib/flowy-document/src/errors.rs
  25. 0 18
      rust-lib/flowy-document/src/event.rs
  26. 0 1
      rust-lib/flowy-document/src/lib.rs
  27. 1 3
      rust-lib/flowy-document/src/module.rs
  28. 60 17
      rust-lib/flowy-document/src/services/doc_controller.rs
  29. 0 36
      rust-lib/flowy-document/tests/editor/doc_test.rs
  30. 0 53
      rust-lib/flowy-document/tests/editor/helper.rs
  31. 1 2
      rust-lib/flowy-document/tests/editor/main.rs
  32. 0 1
      rust-lib/flowy-sdk/src/deps_resolve/editor_deps_impl.rs
  33. 3 3
      rust-lib/flowy-sdk/src/deps_resolve/workspace_deps_impl.rs
  34. 0 1
      rust-lib/flowy-sdk/src/module.rs
  35. 1 1
      rust-lib/flowy-test/src/builder.rs
  36. 6 1
      rust-lib/flowy-user/src/errors.rs
  37. 6 6
      rust-lib/flowy-user/src/services/user/user_session.rs
  38. 3 4
      rust-lib/flowy-workspace/src/entities/view/parser/view_thumbnail.rs
  39. 1 1
      rust-lib/flowy-workspace/src/entities/view/view_create.rs
  40. 15 11
      rust-lib/flowy-workspace/src/module.rs
  41. 7 2
      rust-lib/flowy-workspace/src/services/app_controller.rs
  42. 8 5
      rust-lib/flowy-workspace/src/services/view_controller.rs
  43. 9 16
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  44. 0 1
      rust-lib/flowy-workspace/tests/workspace/app_test.rs
  45. 0 1
      rust-lib/flowy-workspace/tests/workspace/view_test.rs
  46. 1 1
      rust-lib/flowy-workspace/tests/workspace/workspace_test.rs

+ 5 - 1
app_flowy/lib/workspace/application/app/app_bloc.dart

@@ -21,8 +21,12 @@ class AppBloc extends Bloc<AppEvent, AppState> {
         yield* _fetchViews();
       },
       createView: (CreateView value) async* {
-        iAppImpl.createView(
+        final viewOrFailed = await iAppImpl.createView(
             name: value.name, desc: value.desc, viewType: value.viewType);
+        yield viewOrFailed.fold((view) => state, (error) {
+          Log.error(error);
+          return state.copyWith(successOrFailure: right(error));
+        });
       },
     );
   }

+ 6 - 4
app_flowy/lib/workspace/application/view/doc_watch_bloc.dart

@@ -1,7 +1,7 @@
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/workspace/domain/i_doc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/errors.pb.dart';
 part 'doc_watch_bloc.freezed.dart';
 
 class DocWatchBloc extends Bloc<DocWatchEvent, DocWatchState> {
@@ -24,7 +24,9 @@ class DocWatchBloc extends Bloc<DocWatchEvent, DocWatchState> {
     final docOrFail = await iDocImpl.readDoc();
     yield docOrFail.fold(
       (doc) => DocWatchState.loadDoc(doc),
-      (error) => DocWatchState.loadFail(error),
+      (error) {
+        return DocWatchState.loadFail(error);
+      },
     );
   }
 }
@@ -37,6 +39,6 @@ class DocWatchEvent with _$DocWatchEvent {
 @freezed
 class DocWatchState with _$DocWatchState {
   const factory DocWatchState.loading() = Loading;
-  const factory DocWatchState.loadDoc(Doc doc) = LoadDoc;
-  const factory DocWatchState.loadFail(DocError error) = LoadFail;
+  const factory DocWatchState.loadDoc(FlowyDoc doc) = LoadDoc;
+  const factory DocWatchState.loadFail(WorkspaceError error) = LoadFail;
 }

+ 28 - 28
app_flowy/lib/workspace/application/view/doc_watch_bloc.freezed.dart

@@ -154,13 +154,13 @@ class _$DocWatchStateTearOff {
     return const Loading();
   }
 
-  LoadDoc loadDoc(Doc doc) {
+  LoadDoc loadDoc(FlowyDoc doc) {
     return LoadDoc(
       doc,
     );
   }
 
-  LoadFail loadFail(DocError error) {
+  LoadFail loadFail(WorkspaceError error) {
     return LoadFail(
       error,
     );
@@ -175,15 +175,15 @@ mixin _$DocWatchState {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
     required TResult Function() loading,
-    required TResult Function(Doc doc) loadDoc,
-    required TResult Function(DocError error) loadFail,
+    required TResult Function(FlowyDoc doc) loadDoc,
+    required TResult Function(WorkspaceError error) loadFail,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? loading,
-    TResult Function(Doc doc)? loadDoc,
-    TResult Function(DocError error)? loadFail,
+    TResult Function(FlowyDoc doc)? loadDoc,
+    TResult Function(WorkspaceError error)? loadFail,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
@@ -259,8 +259,8 @@ class _$Loading implements Loading {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
     required TResult Function() loading,
-    required TResult Function(Doc doc) loadDoc,
-    required TResult Function(DocError error) loadFail,
+    required TResult Function(FlowyDoc doc) loadDoc,
+    required TResult Function(WorkspaceError error) loadFail,
   }) {
     return loading();
   }
@@ -269,8 +269,8 @@ class _$Loading implements Loading {
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? loading,
-    TResult Function(Doc doc)? loadDoc,
-    TResult Function(DocError error)? loadFail,
+    TResult Function(FlowyDoc doc)? loadDoc,
+    TResult Function(WorkspaceError error)? loadFail,
     required TResult orElse(),
   }) {
     if (loading != null) {
@@ -312,7 +312,7 @@ abstract class Loading implements DocWatchState {
 abstract class $LoadDocCopyWith<$Res> {
   factory $LoadDocCopyWith(LoadDoc value, $Res Function(LoadDoc) then) =
       _$LoadDocCopyWithImpl<$Res>;
-  $Res call({Doc doc});
+  $Res call({FlowyDoc doc});
 }
 
 /// @nodoc
@@ -332,7 +332,7 @@ class _$LoadDocCopyWithImpl<$Res> extends _$DocWatchStateCopyWithImpl<$Res>
       doc == freezed
           ? _value.doc
           : doc // ignore: cast_nullable_to_non_nullable
-              as Doc,
+              as FlowyDoc,
     ));
   }
 }
@@ -343,7 +343,7 @@ class _$LoadDoc implements LoadDoc {
   const _$LoadDoc(this.doc);
 
   @override
-  final Doc doc;
+  final FlowyDoc doc;
 
   @override
   String toString() {
@@ -371,8 +371,8 @@ class _$LoadDoc implements LoadDoc {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
     required TResult Function() loading,
-    required TResult Function(Doc doc) loadDoc,
-    required TResult Function(DocError error) loadFail,
+    required TResult Function(FlowyDoc doc) loadDoc,
+    required TResult Function(WorkspaceError error) loadFail,
   }) {
     return loadDoc(doc);
   }
@@ -381,8 +381,8 @@ class _$LoadDoc implements LoadDoc {
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? loading,
-    TResult Function(Doc doc)? loadDoc,
-    TResult Function(DocError error)? loadFail,
+    TResult Function(FlowyDoc doc)? loadDoc,
+    TResult Function(WorkspaceError error)? loadFail,
     required TResult orElse(),
   }) {
     if (loadDoc != null) {
@@ -417,9 +417,9 @@ class _$LoadDoc implements LoadDoc {
 }
 
 abstract class LoadDoc implements DocWatchState {
-  const factory LoadDoc(Doc doc) = _$LoadDoc;
+  const factory LoadDoc(FlowyDoc doc) = _$LoadDoc;
 
-  Doc get doc => throw _privateConstructorUsedError;
+  FlowyDoc get doc => throw _privateConstructorUsedError;
   @JsonKey(ignore: true)
   $LoadDocCopyWith<LoadDoc> get copyWith => throw _privateConstructorUsedError;
 }
@@ -428,7 +428,7 @@ abstract class LoadDoc implements DocWatchState {
 abstract class $LoadFailCopyWith<$Res> {
   factory $LoadFailCopyWith(LoadFail value, $Res Function(LoadFail) then) =
       _$LoadFailCopyWithImpl<$Res>;
-  $Res call({DocError error});
+  $Res call({WorkspaceError error});
 }
 
 /// @nodoc
@@ -448,7 +448,7 @@ class _$LoadFailCopyWithImpl<$Res> extends _$DocWatchStateCopyWithImpl<$Res>
       error == freezed
           ? _value.error
           : error // ignore: cast_nullable_to_non_nullable
-              as DocError,
+              as WorkspaceError,
     ));
   }
 }
@@ -459,7 +459,7 @@ class _$LoadFail implements LoadFail {
   const _$LoadFail(this.error);
 
   @override
-  final DocError error;
+  final WorkspaceError error;
 
   @override
   String toString() {
@@ -487,8 +487,8 @@ class _$LoadFail implements LoadFail {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
     required TResult Function() loading,
-    required TResult Function(Doc doc) loadDoc,
-    required TResult Function(DocError error) loadFail,
+    required TResult Function(FlowyDoc doc) loadDoc,
+    required TResult Function(WorkspaceError error) loadFail,
   }) {
     return loadFail(error);
   }
@@ -497,8 +497,8 @@ class _$LoadFail implements LoadFail {
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? loading,
-    TResult Function(Doc doc)? loadDoc,
-    TResult Function(DocError error)? loadFail,
+    TResult Function(FlowyDoc doc)? loadDoc,
+    TResult Function(WorkspaceError error)? loadFail,
     required TResult orElse(),
   }) {
     if (loadFail != null) {
@@ -533,9 +533,9 @@ class _$LoadFail implements LoadFail {
 }
 
 abstract class LoadFail implements DocWatchState {
-  const factory LoadFail(DocError error) = _$LoadFail;
+  const factory LoadFail(WorkspaceError error) = _$LoadFail;
 
-  DocError get error => throw _privateConstructorUsedError;
+  WorkspaceError get error => throw _privateConstructorUsedError;
   @JsonKey(ignore: true)
   $LoadFailCopyWith<LoadFail> get copyWith =>
       throw _privateConstructorUsedError;

+ 3 - 3
app_flowy/lib/workspace/application/view/view_list_bloc.dart

@@ -16,7 +16,7 @@ class ViewListBloc extends Bloc<ViewListEvent, ViewListState> {
         yield ViewListState.initial(s.views);
       },
       openView: (s) async* {
-        yield state.copyWith(selectedView: some(s.view.id));
+        yield state.copyWith(openedView: some(s.view.id));
       },
     );
   }
@@ -32,13 +32,13 @@ class ViewListEvent with _$ViewListEvent {
 abstract class ViewListState implements _$ViewListState {
   const factory ViewListState({
     required bool isLoading,
-    required Option<String> selectedView,
+    required Option<String> openedView,
     required Option<List<View>> views,
   }) = _ViewListState;
 
   factory ViewListState.initial(List<View> views) => ViewListState(
         isLoading: false,
-        selectedView: none(),
+        openedView: none(),
         views: some(views),
       );
 }

+ 21 - 23
app_flowy/lib/workspace/application/view/view_list_bloc.freezed.dart

@@ -310,11 +310,11 @@ class _$ViewListStateTearOff {
 
   _ViewListState call(
       {required bool isLoading,
-      required Option<String> selectedView,
+      required Option<String> openedView,
       required Option<List<View>> views}) {
     return _ViewListState(
       isLoading: isLoading,
-      selectedView: selectedView,
+      openedView: openedView,
       views: views,
     );
   }
@@ -326,7 +326,7 @@ const $ViewListState = _$ViewListStateTearOff();
 /// @nodoc
 mixin _$ViewListState {
   bool get isLoading => throw _privateConstructorUsedError;
-  Option<String> get selectedView => throw _privateConstructorUsedError;
+  Option<String> get openedView => throw _privateConstructorUsedError;
   Option<List<View>> get views => throw _privateConstructorUsedError;
 
   @JsonKey(ignore: true)
@@ -340,7 +340,7 @@ abstract class $ViewListStateCopyWith<$Res> {
           ViewListState value, $Res Function(ViewListState) then) =
       _$ViewListStateCopyWithImpl<$Res>;
   $Res call(
-      {bool isLoading, Option<String> selectedView, Option<List<View>> views});
+      {bool isLoading, Option<String> openedView, Option<List<View>> views});
 }
 
 /// @nodoc
@@ -355,7 +355,7 @@ class _$ViewListStateCopyWithImpl<$Res>
   @override
   $Res call({
     Object? isLoading = freezed,
-    Object? selectedView = freezed,
+    Object? openedView = freezed,
     Object? views = freezed,
   }) {
     return _then(_value.copyWith(
@@ -363,9 +363,9 @@ class _$ViewListStateCopyWithImpl<$Res>
           ? _value.isLoading
           : isLoading // ignore: cast_nullable_to_non_nullable
               as bool,
-      selectedView: selectedView == freezed
-          ? _value.selectedView
-          : selectedView // ignore: cast_nullable_to_non_nullable
+      openedView: openedView == freezed
+          ? _value.openedView
+          : openedView // ignore: cast_nullable_to_non_nullable
               as Option<String>,
       views: views == freezed
           ? _value.views
@@ -383,7 +383,7 @@ abstract class _$ViewListStateCopyWith<$Res>
       __$ViewListStateCopyWithImpl<$Res>;
   @override
   $Res call(
-      {bool isLoading, Option<String> selectedView, Option<List<View>> views});
+      {bool isLoading, Option<String> openedView, Option<List<View>> views});
 }
 
 /// @nodoc
@@ -400,7 +400,7 @@ class __$ViewListStateCopyWithImpl<$Res>
   @override
   $Res call({
     Object? isLoading = freezed,
-    Object? selectedView = freezed,
+    Object? openedView = freezed,
     Object? views = freezed,
   }) {
     return _then(_ViewListState(
@@ -408,9 +408,9 @@ class __$ViewListStateCopyWithImpl<$Res>
           ? _value.isLoading
           : isLoading // ignore: cast_nullable_to_non_nullable
               as bool,
-      selectedView: selectedView == freezed
-          ? _value.selectedView
-          : selectedView // ignore: cast_nullable_to_non_nullable
+      openedView: openedView == freezed
+          ? _value.openedView
+          : openedView // ignore: cast_nullable_to_non_nullable
               as Option<String>,
       views: views == freezed
           ? _value.views
@@ -424,20 +424,18 @@ class __$ViewListStateCopyWithImpl<$Res>
 
 class _$_ViewListState implements _ViewListState {
   const _$_ViewListState(
-      {required this.isLoading,
-      required this.selectedView,
-      required this.views});
+      {required this.isLoading, required this.openedView, required this.views});
 
   @override
   final bool isLoading;
   @override
-  final Option<String> selectedView;
+  final Option<String> openedView;
   @override
   final Option<List<View>> views;
 
   @override
   String toString() {
-    return 'ViewListState(isLoading: $isLoading, selectedView: $selectedView, views: $views)';
+    return 'ViewListState(isLoading: $isLoading, openedView: $openedView, views: $views)';
   }
 
   @override
@@ -447,9 +445,9 @@ class _$_ViewListState implements _ViewListState {
             (identical(other.isLoading, isLoading) ||
                 const DeepCollectionEquality()
                     .equals(other.isLoading, isLoading)) &&
-            (identical(other.selectedView, selectedView) ||
+            (identical(other.openedView, openedView) ||
                 const DeepCollectionEquality()
-                    .equals(other.selectedView, selectedView)) &&
+                    .equals(other.openedView, openedView)) &&
             (identical(other.views, views) ||
                 const DeepCollectionEquality().equals(other.views, views)));
   }
@@ -458,7 +456,7 @@ class _$_ViewListState implements _ViewListState {
   int get hashCode =>
       runtimeType.hashCode ^
       const DeepCollectionEquality().hash(isLoading) ^
-      const DeepCollectionEquality().hash(selectedView) ^
+      const DeepCollectionEquality().hash(openedView) ^
       const DeepCollectionEquality().hash(views);
 
   @JsonKey(ignore: true)
@@ -470,13 +468,13 @@ class _$_ViewListState implements _ViewListState {
 abstract class _ViewListState implements ViewListState {
   const factory _ViewListState(
       {required bool isLoading,
-      required Option<String> selectedView,
+      required Option<String> openedView,
       required Option<List<View>> views}) = _$_ViewListState;
 
   @override
   bool get isLoading => throw _privateConstructorUsedError;
   @override
-  Option<String> get selectedView => throw _privateConstructorUsedError;
+  Option<String> get openedView => throw _privateConstructorUsedError;
   @override
   Option<List<View>> get views => throw _privateConstructorUsedError;
   @override

+ 8 - 9
app_flowy/lib/workspace/domain/i_doc.dart

@@ -1,18 +1,17 @@
 import 'package:flowy_editor/flowy_editor.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/doc_create.pb.dart';
 import 'package:dartz/dartz.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 
-class Doc {
-  final DocInfo info;
+class FlowyDoc {
+  final Doc doc;
   final Document data;
 
-  Doc({required this.info, required this.data});
+  FlowyDoc({required this.doc, required this.data});
 }
 
 abstract class IDoc {
-  Future<Either<Doc, DocError>> readDoc();
-  Future<Either<Unit, DocError>> updateDoc(
-      {String? name, String? desc, String? text});
-  Future<Either<Unit, DocError>> closeDoc();
+  Future<Either<FlowyDoc, WorkspaceError>> readDoc();
+  Future<Either<Unit, WorkspaceError>> updateDoc({String? text});
+  Future<Either<Unit, WorkspaceError>> closeDoc();
 }

+ 1 - 17
app_flowy/lib/workspace/infrastructure/i_app_impl.dart

@@ -1,5 +1,4 @@
 import 'package:app_flowy/workspace/infrastructure/repos/app_repo.dart';
-import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart' as workspace;
 import 'package:app_flowy/workspace/domain/i_app.dart';
@@ -22,26 +21,11 @@ class IAppImpl extends IApp {
       {required String name, String? desc, required ViewType viewType}) {
     return repo.createView(name, desc ?? "", viewType).then((result) {
       return result.fold(
-        (view) => _createDoc(view),
+        (view) => left(view),
         (r) => right(r),
       );
     });
   }
-
-  Future<Either<View, workspace.WorkspaceError>> _createDoc(View view) async {
-    switch (view.viewType) {
-      case ViewType.Doc:
-        final docRepo = DocRepository(docId: view.id);
-        final result = await docRepo.createDoc(
-            name: view.name, desc: "", text: "[{\"insert\":\"\\n\"}]");
-        return result.fold((l) => left(view), (r) {
-          return right(workspace.WorkspaceError(
-              code: workspace.ErrorCode.Unknown, msg: r.msg));
-        });
-      default:
-        return left(view);
-    }
-  }
 }
 
 class IAppWatchImpl extends IAppWatch {

+ 10 - 23
app_flowy/lib/workspace/infrastructure/i_doc_impl.dart

@@ -2,10 +2,9 @@ import 'dart:convert';
 
 import 'package:dartz/dartz.dart';
 import 'package:flowy_editor/flowy_editor.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/errors.pb.dart';
-
 import 'package:app_flowy/workspace/domain/i_doc.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 
 class IDocImpl extends IDoc {
   DocRepository repo;
@@ -13,35 +12,23 @@ class IDocImpl extends IDoc {
   IDocImpl({required this.repo});
 
   @override
-  Future<Either<Unit, DocError>> closeDoc() {
+  Future<Either<Unit, WorkspaceError>> closeDoc() {
     return repo.closeDoc();
   }
 
   @override
-  Future<Either<Doc, DocError>> readDoc() async {
-    final docInfoOrFail = await repo.readDoc();
-    return docInfoOrFail.fold(
-      (info) => _loadDocument(info.path).then((result) => result.fold(
-          (document) => left(Doc(info: info, data: document)),
-          (error) => right(error))),
-      (error) => right(error),
-    );
+  Future<Either<FlowyDoc, WorkspaceError>> readDoc() async {
+    final docOrFail = await repo.readDoc();
+
+    return docOrFail.fold((doc) {
+      return left(FlowyDoc(doc: doc, data: _decodeToDocument(doc.data)));
+    }, (error) => right(error));
   }
 
   @override
-  Future<Either<Unit, DocError>> updateDoc(
-      {String? name, String? desc, String? text}) {
+  Future<Either<Unit, WorkspaceError>> updateDoc({String? text}) {
     final json = jsonEncode(text ?? "");
-    return repo.updateDoc(name: name, desc: desc, text: json);
-  }
-
-  Future<Either<Document, DocError>> _loadDocument(String path) {
-    return repo.readDocData(path).then((docDataOrFail) {
-      return docDataOrFail.fold(
-        (docData) => left(_decodeToDocument(docData.text)),
-        (error) => right(error),
-      );
-    });
+    return repo.updateDoc(text: json);
   }
 
   Document _decodeToDocument(String text) {

+ 13 - 28
app_flowy/lib/workspace/infrastructure/repos/doc_repo.dart

@@ -1,9 +1,9 @@
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/doc_create.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/doc_modify.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/doc_query.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-document/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/view_query.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/view_update.pb.dart';
 
 class DocRepository {
   final String docId;
@@ -11,34 +11,19 @@ class DocRepository {
     required this.docId,
   });
 
-  Future<Either<DocInfo, DocError>> createDoc(
-      {required String name, String? desc, String? text}) {
-    final request =
-        CreateDocRequest(id: docId, name: name, desc: desc, text: text);
-
-    return EditorEventCreateDoc(request).send();
-  }
-
-  Future<Either<DocInfo, DocError>> readDoc() {
-    final request = QueryDocRequest.create()..docId = docId;
-    return EditorEventReadDocInfo(request).send();
+  Future<Either<Doc, WorkspaceError>> readDoc() {
+    final request = OpenViewRequest.create()..viewId = docId;
+    return WorkspaceEventOpenView(request).send();
   }
 
-  Future<Either<DocData, DocError>> readDocData(String path) {
-    final request = QueryDocDataRequest.create()
-      ..docId = docId
-      ..path = path;
-    return EditorEventReadDocData(request).send();
-  }
-
-  Future<Either<Unit, DocError>> updateDoc(
-      {String? name, String? desc, String? text}) {
-    final request = UpdateDocRequest(id: docId, name: name, text: text);
-
-    return EditorEventUpdateDoc(request).send();
+  Future<Either<Unit, WorkspaceError>> updateDoc({String? text}) {
+    final request = UpdateViewDataRequest.create()
+      ..viewId = docId
+      ..data = text ?? "";
+    return WorkspaceEventUpdateViewData(request).send();
   }
 
-  Future<Either<Unit, DocError>> closeDoc(
+  Future<Either<Unit, WorkspaceError>> closeDoc(
       {String? name, String? desc, String? text}) {
     throw UnimplementedError();
   }

+ 2 - 0
app_flowy/lib/workspace/presentation/app/view_list_page.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/workspace/presentation/app/app_page.dart';
+import 'package:flowy_infra/flowy_logger.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
@@ -58,6 +59,7 @@ class ViewListPage extends StatelessWidget {
         viewCtx: viewCtx,
         isSelected: _isViewSelected(context, view.id),
         onOpen: (view) {
+          Log.debug("Open view: $view");
           context.read<ViewListNotifier>().setSelectedView(view);
           final stackView = stackViewFromView(viewCtx.view);
           getIt<HomePageStack>().setStackView(stackView);

+ 3 - 3
app_flowy/lib/workspace/presentation/doc/editor_page.dart

@@ -11,20 +11,20 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 class EditorPage extends StatelessWidget {
   final FocusNode _focusNode = FocusNode();
   late EditorController controller;
-  final Doc doc;
+  final FlowyDoc doc;
 
   EditorPage({Key? key, required this.doc}) : super(key: key) {
     controller = EditorController(
       document: doc.data,
       selection: const TextSelection.collapsed(offset: 0),
-      persistence: getIt<EditorPersistence>(param1: doc.info.id),
+      persistence: getIt<EditorPersistence>(param1: doc.doc.id),
     );
   }
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<DocBloc>(param1: doc.info.id),
+      create: (context) => getIt<DocBloc>(param1: doc.doc.id),
       child: BlocBuilder<DocBloc, DocState>(
         builder: (ctx, state) {
           return Column(

+ 1 - 0
app_flowy/lib/workspace/presentation/home/home_screen.dart

@@ -36,6 +36,7 @@ class HomeScreen extends StatelessWidget {
               loading: (_) {},
               unauthorized: (unauthorized) {
                 // TODO: push to login screen when user token was invalid
+                Log.error("Push to login screen when user token was invalid");
               },
             );
           },

+ 1 - 1
app_flowy/packages/flowy_infra/lib/flowy_logger.dart

@@ -8,7 +8,7 @@ class Log {
   Log() {
     _logger = Logger(
       printer: PrettyPrinter(
-          methodCount: 0, // number of method calls to be displayed
+          methodCount: 2, // number of method calls to be displayed
           errorMethodCount:
               8, // number of method calls if stacktrace is provided
           lineLength: 120, // width of the output

+ 13 - 47
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -237,70 +237,36 @@ class WorkspaceEventDeleteView {
     }
 }
 
-class EditorEventCreateDoc {
-     CreateDocRequest request;
-     EditorEventCreateDoc(this.request);
+class WorkspaceEventOpenView {
+     OpenViewRequest request;
+     WorkspaceEventOpenView(this.request);
 
-    Future<Either<DocInfo, DocError>> send() {
+    Future<Either<Doc, WorkspaceError>> send() {
     final request = FFIRequest.create()
-          ..event = EditorEvent.CreateDoc.toString()
+          ..event = WorkspaceEvent.OpenView.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(DocInfo.fromBuffer(okBytes)),
-           (errBytes) => right(DocError.fromBuffer(errBytes)),
+           (okBytes) => left(Doc.fromBuffer(okBytes)),
+           (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
         ));
     }
 }
 
-class EditorEventUpdateDoc {
-     UpdateDocRequest request;
-     EditorEventUpdateDoc(this.request);
+class WorkspaceEventUpdateViewData {
+     UpdateViewDataRequest request;
+     WorkspaceEventUpdateViewData(this.request);
 
-    Future<Either<Unit, DocError>> send() {
+    Future<Either<Unit, WorkspaceError>> send() {
     final request = FFIRequest.create()
-          ..event = EditorEvent.UpdateDoc.toString()
+          ..event = WorkspaceEvent.UpdateViewData.toString()
           ..payload = requestToBytes(this.request);
 
     return Dispatch.asyncRequest(request)
         .then((bytesResult) => bytesResult.fold(
            (bytes) => left(unit),
-           (errBytes) => right(DocError.fromBuffer(errBytes)),
-        ));
-    }
-}
-
-class EditorEventReadDocInfo {
-     QueryDocRequest request;
-     EditorEventReadDocInfo(this.request);
-
-    Future<Either<DocInfo, DocError>> send() {
-    final request = FFIRequest.create()
-          ..event = EditorEvent.ReadDocInfo.toString()
-          ..payload = requestToBytes(this.request);
-
-    return Dispatch.asyncRequest(request)
-        .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(DocInfo.fromBuffer(okBytes)),
-           (errBytes) => right(DocError.fromBuffer(errBytes)),
-        ));
-    }
-}
-
-class EditorEventReadDocData {
-     QueryDocDataRequest request;
-     EditorEventReadDocData(this.request);
-
-    Future<Either<DocData, DocError>> send() {
-    final request = FFIRequest.create()
-          ..event = EditorEvent.ReadDocData.toString()
-          ..payload = requestToBytes(this.request);
-
-    return Dispatch.asyncRequest(request)
-        .then((bytesResult) => bytesResult.fold(
-           (okBytes) => left(DocData.fromBuffer(okBytes)),
-           (errBytes) => right(DocError.fromBuffer(errBytes)),
+           (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
         ));
     }
 }

+ 1 - 1
backend/src/service/doc_service/router.rs

@@ -4,7 +4,7 @@ use actix_web::{
 };
 use sqlx::PgPool;
 
-use flowy_document::protobuf::{CreateDocParams, QueryDocParams, UpdateDocParams};
+use flowy_document::protobuf::{QueryDocParams, UpdateDocParams};
 use flowy_net::errors::ServerError;
 
 use crate::service::{

+ 14 - 12
backend/src/service/workspace_service/user_default/user_default.rs

@@ -1,10 +1,10 @@
 use flowy_net::errors::ServerError;
-use flowy_workspace::protobuf::{App, View, ViewType, Workspace};
+use flowy_workspace::protobuf::{App, CreateViewParams, View, ViewType, Workspace};
 
 use crate::{
     service::workspace_service::{
         app::sql_builder::NewAppSqlBuilder as AppBuilder,
-        view::sql_builder::NewViewSqlBuilder as ViewBuilder,
+        view::{create_view_with_transaction, sql_builder::NewViewSqlBuilder as ViewBuilder},
         workspace::sql_builder::NewWorkspaceBuilder as WorkspaceBuilder,
     },
     sqlx_ext::{map_sqlx_error, DBTransaction},
@@ -57,16 +57,18 @@ async fn create_app(
 }
 
 async fn create_view(transaction: &mut DBTransaction<'_>, app: &App) -> Result<View, ServerError> {
-    let (sql, args, view) = ViewBuilder::new(&app.id)
-        .name("DefaultView")
-        .desc("View created by AppFlowy")
-        .thumbnail("https://view.png")
-        .view_type(ViewType::Doc)
-        .build()?;
+    let params = CreateViewParams {
+        belong_to_id: app.id.clone(),
+        name: "DefaultView".to_string(),
+        desc: "View created by AppFlowy".to_string(),
+        thumbnail: "123.png".to_string(),
+        view_type: ViewType::Doc,
+        data: "[{\"insert\":\"\\n\"}]".to_string(),
+        unknown_fields: Default::default(),
+        cached_size: Default::default(),
+    };
+
+    let view = create_view_with_transaction(transaction, params).await?;
 
-    let _ = sqlx::query_with(&sql, args)
-        .execute(transaction)
-        .await
-        .map_err(map_sqlx_error)?;
     Ok(view)
 }

+ 20 - 14
backend/src/service/workspace_service/view/view.rs

@@ -28,15 +28,27 @@ pub(crate) async fn create_view(
     pool: &PgPool,
     params: CreateViewParams,
 ) -> Result<FlowyResponse, ServerError> {
-    let name = ViewName::parse(params.name).map_err(invalid_params)?;
-    let belong_to_id = AppId::parse(params.belong_to_id).map_err(invalid_params)?;
-    let thumbnail = ViewThumbnail::parse(params.thumbnail).map_err(invalid_params)?;
-    let desc = ViewDesc::parse(params.desc).map_err(invalid_params)?;
-
     let mut transaction = pool
         .begin()
         .await
         .context("Failed to acquire a Postgres connection to create view")?;
+    let view = create_view_with_transaction(&mut transaction, params).await?;
+    transaction
+        .commit()
+        .await
+        .context("Failed to commit SQL transaction to create view.")?;
+
+    FlowyResponse::success().pb(view)
+}
+
+pub(crate) async fn create_view_with_transaction(
+    transaction: &mut DBTransaction<'_>,
+    params: CreateViewParams,
+) -> Result<View, ServerError> {
+    let name = ViewName::parse(params.name).map_err(invalid_params)?;
+    let belong_to_id = AppId::parse(params.belong_to_id).map_err(invalid_params)?;
+    let thumbnail = ViewThumbnail::parse(params.thumbnail).map_err(invalid_params)?;
+    let desc = ViewDesc::parse(params.desc).map_err(invalid_params)?;
 
     let (sql, args, view) = NewViewSqlBuilder::new(belong_to_id.as_ref())
         .name(name.as_ref())
@@ -46,21 +58,15 @@ pub(crate) async fn create_view(
         .build()?;
 
     let _ = sqlx::query_with(&sql, args)
-        .execute(&mut transaction)
+        .execute(transaction as &mut DBTransaction<'_>)
         .await
         .map_err(map_sqlx_error)?;
 
     let mut create_doc_params = CreateDocParams::new();
     create_doc_params.set_data(params.data);
     create_doc_params.set_id(view.id.clone());
-    let _ = create_doc(&mut transaction, create_doc_params).await?;
-
-    transaction
-        .commit()
-        .await
-        .context("Failed to commit SQL transaction to create view.")?;
-
-    FlowyResponse::success().pb(view)
+    let _ = create_doc(transaction, create_doc_params).await?;
+    Ok(view)
 }
 
 pub(crate) async fn read_view(

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

@@ -4,7 +4,7 @@ use backend::{
 };
 
 use flowy_document::{
-    entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams},
+    entities::doc::{Doc, QueryDocParams, UpdateDocParams},
     prelude::*,
 };
 use flowy_user::{errors::UserError, prelude::*};

+ 1 - 1
backend/tests/api/workspace.rs

@@ -1,7 +1,7 @@
 use crate::helper::*;
 use flowy_workspace::entities::{
     app::{DeleteAppParams, QueryAppParams, UpdateAppParams},
-    view::{CreateViewParams, DeleteViewParams, QueryViewParams, UpdateViewParams, View, ViewType},
+    view::{DeleteViewParams, QueryViewParams, UpdateViewParams},
     workspace::{
         CreateWorkspaceParams,
         DeleteWorkspaceParams,

+ 1 - 1
rust-lib/flowy-database/src/lib.rs

@@ -14,7 +14,7 @@ pub use diesel_derives::*;
 #[macro_use]
 extern crate diesel_migrations;
 
-pub use flowy_sqlite::{DBConnection, Database};
+pub use flowy_sqlite::{ConnectionPool, DBConnection, Database};
 pub type Error = diesel::result::Error;
 
 use diesel_migrations::*;

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

@@ -4,10 +4,8 @@ use std::sync::Once;
 #[allow(dead_code)]
 pub fn setup_env() {
     static INIT: Once = Once::new();
-    INIT.call_once(|| {
-        std::env::set_var("RUST_LOG", "flowy_dispatch=debug,debug");
-        env_logger::init();
-    });
+    std::env::);
+    INIT.call_once(|| env_logger::init());
 }
 
 pub fn init_dispatch<F>(module_factory: F) -> EventDispatch

+ 1 - 1
rust-lib/flowy-document/Flowy.toml

@@ -1,3 +1,3 @@
 
 proto_crates = ["src/entities", "src/event.rs", "src/errors.rs", "src/observable"]
-event_files = ["src/event.rs"]
+event_files = []

+ 0 - 4
rust-lib/flowy-document/src/entities/doc/doc.rs

@@ -1,7 +1,3 @@
-use crate::{
-    entities::doc::parser::*,
-    errors::{ErrorBuilder, *},
-};
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
 

+ 17 - 1
rust-lib/flowy-document/src/errors.rs

@@ -17,6 +17,8 @@ pub struct DocError {
 
 impl DocError {
     fn new(code: ErrorCode, msg: &str) -> Self { Self { code, msg: msg.to_owned() } }
+
+    pub fn is_record_not_found(&self) -> bool { self.code == ErrorCode::DocNotfound }
 }
 
 #[derive(Debug, Clone, ProtoBuf_Enum, Display, PartialEq, Eq)]
@@ -39,13 +41,26 @@ impl std::default::Default for ErrorCode {
 }
 
 impl std::convert::From<flowy_database::Error> for DocError {
-    fn from(error: flowy_database::Error) -> Self { ErrorBuilder::new(ErrorCode::InternalError).error(error).build() }
+    fn from(error: flowy_database::Error) -> Self {
+        match error {
+            flowy_database::Error::NotFound => ErrorBuilder::new(ErrorCode::DocNotfound).error(error).build(),
+            _ => ErrorBuilder::new(ErrorCode::InternalError).error(error).build(),
+        }
+    }
 }
 
+// impl std::convert::From<::r2d2::Error> for DocError {
+//     fn from(error: r2d2::Error) -> Self {
+// ErrorBuilder::new(ErrorCode::InternalError).error(error).build() } }
+
 impl std::convert::From<FileError> for DocError {
     fn from(error: FileError) -> Self { ErrorBuilder::new(ErrorCode::InternalError).error(error).build() }
 }
 
+// impl std::convert::From<flowy_sqlite::Error> for DocError {
+//     fn from(error: flowy_sqlite::Error) -> Self {
+// ErrorBuilder::new(ErrorCode::InternalError).error(error).build() } }
+
 impl std::convert::From<flowy_net::errors::ServerError> for DocError {
     fn from(error: ServerError) -> Self {
         let code = server_error_to_doc_error(error.code);
@@ -54,6 +69,7 @@ impl std::convert::From<flowy_net::errors::ServerError> for DocError {
 }
 
 use flowy_net::errors::ErrorCode as ServerErrorCode;
+
 fn server_error_to_doc_error(code: ServerErrorCode) -> ErrorCode {
     match code {
         ServerErrorCode::UserUnauthorized => ErrorCode::UserUnauthorized,

+ 0 - 18
rust-lib/flowy-document/src/event.rs

@@ -1,18 +0,0 @@
-use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
-use strum_macros::Display;
-
-#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
-#[event_err = "DocError"]
-pub enum EditorEvent {
-    #[event(input = "CreateDocRequest")]
-    CreateDoc = 0,
-
-    #[event(input = "UpdateDocRequest")]
-    UpdateDoc = 1,
-
-    #[event(input = "QueryDocRequest", output = "Doc")]
-    ReadDoc   = 2,
-
-    #[event(input = "QueryDocRequest")]
-    DeleteDoc = 3,
-}

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

@@ -1,6 +1,5 @@
 pub mod entities;
 pub mod errors;
-pub mod event;
 pub mod module;
 mod observable;
 pub mod protobuf;

+ 1 - 3
rust-lib/flowy-document/src/module.rs

@@ -1,10 +1,8 @@
 use crate::{
     errors::DocError,
-    event::EditorEvent,
     services::{doc_controller::DocController, file_manager::FileManager, server::construct_doc_server},
 };
-use flowy_database::DBConnection;
-use flowy_dispatch::prelude::*;
+
 use std::sync::Arc;
 use tokio::sync::RwLock;
 

+ 60 - 17
rust-lib/flowy-document/src/services/doc_controller.rs

@@ -1,12 +1,14 @@
 use crate::{
     entities::doc::{CreateDocParams, Doc, QueryDocParams, UpdateDocParams},
-    errors::DocError,
+    errors::{DocError, ErrorBuilder, ErrorCode},
     module::DocumentUser,
     services::server::Server,
     sql_tables::doc::{DocTable, DocTableChangeset, DocTableSql},
 };
-use flowy_database::SqliteConnection;
+use flowy_database::{ConnectionPool, SqliteConnection};
+
 use std::sync::Arc;
+use tokio::task::JoinHandle;
 
 pub struct DocController {
     server: Server,
@@ -39,11 +41,12 @@ impl DocController {
         Ok(())
     }
 
-    #[tracing::instrument(level = "debug", skip(self, conn), err)]
-    pub fn open(&self, params: QueryDocParams, conn: &SqliteConnection) -> Result<Doc, DocError> {
-        let doc: Doc = self.sql.read_doc_table(&params.doc_id, conn)?.into();
-        let _ = self.read_doc_on_server(params)?;
-        Ok(doc)
+    #[tracing::instrument(level = "debug", skip(self, pool), err)]
+    pub async fn open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Doc, DocError> {
+        match self._open(params.clone(), pool.clone()) {
+            Ok(doc_table) => Ok(doc_table.into()),
+            Err(error) => self.try_read_on_server(params, pool.clone(), error).await,
+        }
     }
 
     #[tracing::instrument(level = "debug", skip(self, conn), err)]
@@ -71,19 +74,36 @@ impl DocController {
         Ok(())
     }
 
-    #[tracing::instrument(level = "debug", skip(self), err)]
-    fn read_doc_on_server(&self, params: QueryDocParams) -> Result<(), DocError> {
+    #[tracing::instrument(level = "debug", skip(self, pool), err)]
+    fn read_doc_from_server(
+        &self,
+        params: QueryDocParams,
+        pool: Arc<ConnectionPool>,
+    ) -> Result<JoinHandle<Result<Doc, DocError>>, DocError> {
         let token = self.user.token()?;
         let server = self.server.clone();
-        tokio::spawn(async move {
-            // Opti: handle the error and retry?
-            let _doc = server.read_doc(&token, params).await?;
-            // save to disk
-            // notify
+        let sql = self.sql.clone();
 
-            Result::<(), DocError>::Ok(())
-        });
-        Ok(())
+        Ok(tokio::spawn(async move {
+            match server.read_doc(&token, params).await? {
+                None => Err(ErrorBuilder::new(ErrorCode::DocNotfound).build()),
+                Some(doc) => {
+                    let doc_table = DocTable::new(doc.clone());
+                    let _ = sql.create_doc_table(doc_table, &*(pool.get().unwrap()))?;
+                    // TODO: notify
+                    Ok(doc)
+                },
+            }
+        }))
+    }
+
+    #[tracing::instrument(level = "debug", skip(self), err)]
+    async fn sync_read_doc_from_server(&self, params: QueryDocParams) -> Result<Doc, DocError> {
+        let token = self.user.token()?;
+        match self.server.read_doc(&token, params).await? {
+            None => Err(ErrorBuilder::new(ErrorCode::DocNotfound).build()),
+            Some(doc) => Ok(doc),
+        }
     }
 
     #[tracing::instrument(level = "debug", skip(self), err)]
@@ -101,4 +121,27 @@ impl DocController {
         });
         Ok(())
     }
+
+    fn _open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Doc, DocError> {
+        let doc_table = self.sql.read_doc_table(&params.doc_id, &*(pool.get().unwrap()))?;
+        let doc: Doc = doc_table.into();
+        let _ = self.read_doc_from_server(params, pool.clone())?;
+        Ok(doc)
+    }
+
+    async fn try_read_on_server(&self, params: QueryDocParams, pool: Arc<ConnectionPool>, error: DocError) -> Result<Doc, DocError> {
+        if error.is_record_not_found() {
+            log::debug!("Doc:{} don't exist, reading from server", params.doc_id);
+            self.read_doc_from_server(params, pool)?.await.map_err(internal_error)?
+        } else {
+            Err(error)
+        }
+    }
+}
+
+fn internal_error<T>(e: T) -> DocError
+where
+    T: std::fmt::Debug,
+{
+    ErrorBuilder::new(ErrorCode::InternalError).error(e).build()
 }

+ 0 - 36
rust-lib/flowy-document/tests/editor/doc_test.rs

@@ -1,36 +0,0 @@
-use crate::helper::*;
-use flowy_test::FlowyEnv;
-
-#[test]
-fn doc_create_test() {
-    let sdk = FlowyEnv::setup().sdk;
-    let doc = create_doc(&sdk, "flutter ❤️ rust");
-    dbg!(&doc);
-
-    let doc = read_doc_data(&sdk, &doc.id);
-    assert_eq!(doc.data, "flutter ❤️ rust".to_owned());
-}
-
-#[test]
-fn doc_update_test() {
-    let sdk = FlowyEnv::setup().sdk;
-    let doc_desc = create_doc(&sdk, "flutter ❤️ rust");
-    dbg!(&doc_desc);
-
-    let content = "😁😁😁😁😁😁😁😁😁😁".to_owned();
-    save_doc(&sdk, &doc_desc, &content);
-
-    let doc = read_doc_data(&sdk, &doc_desc.id);
-    assert_eq!(doc.data, content);
-}
-
-#[test]
-fn doc_update_big_data_test() {
-    let sdk = FlowyEnv::setup().sdk;
-    let doc_desc = create_doc(&sdk, "");
-    let content = "flutter ❤️ rust".repeat(1000000);
-    save_doc(&sdk, &doc_desc, &content);
-
-    let doc = read_doc_data(&sdk, &doc_desc.id);
-    assert_eq!(doc.data, content);
-}

+ 0 - 53
rust-lib/flowy-document/tests/editor/helper.rs

@@ -1,53 +0,0 @@
-use flowy_test::builder::DocTest;
-
-use flowy_document::{entities::doc::*, event::EditorEvent::*};
-use flowy_infra::uuid;
-use flowy_test::prelude::*;
-
-pub fn create_doc(sdk: &FlowyTestSDK, text: &str) -> Doc {
-    let request = CreateDocRequest {
-        id: uuid(),
-        data: text.to_owned(),
-    };
-
-    let doc = DocTest::new(sdk.clone())
-        .event(CreateDoc)
-        .request(request)
-        .sync_send()
-        .parse::<Doc>();
-    doc
-}
-
-pub fn save_doc(sdk: &FlowyTestSDK, doc: &Doc, content: &str) {
-    let request = UpdateDocRequest {
-        id: doc.id.clone(),
-        data: Some(content.to_owned()),
-    };
-
-    let _ = DocTest::new(sdk.clone()).event(UpdateDoc).request(request).sync_send();
-}
-
-// #[allow(dead_code)]
-// pub fn read_doc(doc_id: &str) -> DocInfo {
-//     let request = QueryDocRequest {
-//         doc_id: doc_id.to_string(),
-//     };
-//
-//     let doc = AnnieTestBuilder::new()
-//         .event(ReadDocInfo)
-//         .request(request)
-//         .sync_send()
-//         .parse::<DocInfo>();
-//
-//     doc
-// }
-
-pub(crate) fn read_doc_data(sdk: &FlowyTestSDK, doc_id: &str) -> Doc {
-    let request = QueryDocRequest {
-        doc_id: doc_id.to_string(),
-    };
-
-    let doc = DocTest::new(sdk.clone()).event(ReadDoc).request(request).sync_send().parse::<Doc>();
-
-    doc
-}

+ 1 - 2
rust-lib/flowy-document/tests/editor/main.rs

@@ -1,2 +1 @@
-// mod doc_test;
-// mod helper;
+

+ 0 - 1
rust-lib/flowy-sdk/src/deps_resolve/editor_deps_impl.rs

@@ -1,4 +1,3 @@
-use flowy_database::DBConnection;
 use flowy_document::{
     errors::{DocError, ErrorBuilder, ErrorCode},
     module::DocumentUser,

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

@@ -1,4 +1,4 @@
-use flowy_database::DBConnection;
+use flowy_database::ConnectionPool;
 use flowy_user::services::user::UserSession;
 use flowy_workspace::{
     errors::{ErrorBuilder, ErrorCode, WorkspaceError},
@@ -29,9 +29,9 @@ pub struct WorkspaceDatabaseImpl {
 }
 
 impl WorkspaceDatabase for WorkspaceDatabaseImpl {
-    fn db_connection(&self) -> Result<DBConnection, WorkspaceError> {
+    fn db_pool(&self) -> Result<Arc<ConnectionPool>, WorkspaceError> {
         self.user_session
-            .db_conn()
+            .db_pool()
             .map_err(|e| ErrorBuilder::new(ErrorCode::InternalError).error(e).build())
     }
 }

+ 0 - 1
rust-lib/flowy-sdk/src/module.rs

@@ -1,5 +1,4 @@
 use flowy_dispatch::prelude::Module;
-use flowy_user::prelude::*;
 
 use crate::deps_resolve::{EditorUserImpl, WorkspaceDatabaseImpl, WorkspaceUserImpl};
 use flowy_document::module::Document;

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

@@ -7,7 +7,7 @@ use std::{
 
 use crate::FlowyTestSDK;
 use flowy_dispatch::prelude::*;
-use flowy_document::errors::DocError;
+
 use flowy_sdk::*;
 use flowy_user::errors::UserError;
 use flowy_workspace::errors::WorkspaceError;

+ 6 - 1
rust-lib/flowy-user/src/errors.rs

@@ -83,7 +83,12 @@ 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::InternalError).error(error).build() }
+    fn from(error: flowy_database::Error) -> Self {
+        match error {
+            flowy_database::Error::NotFound => ErrorBuilder::new(ErrorCode::UserNotExist).error(error).build(),
+            _ => ErrorBuilder::new(ErrorCode::InternalError).error(error).build(),
+        }
+    }
 }
 
 impl std::convert::From<::r2d2::Error> for UserError {

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

@@ -54,7 +54,7 @@ impl UserSession {
         }
     }
 
-    pub fn db_conn(&self) -> Result<DBConnection, UserError> {
+    pub fn db_connection(&self) -> Result<DBConnection, UserError> {
         let user_id = self.get_session()?.user_id;
         self.database.get_connection(&user_id)
     }
@@ -101,7 +101,7 @@ impl UserSession {
     #[tracing::instrument(level = "debug", skip(self))]
     pub async fn sign_out(&self) -> Result<(), UserError> {
         let session = self.get_session()?;
-        let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))).execute(&*(self.db_conn()?))?;
+        let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))).execute(&*(self.db_connection()?))?;
         let _ = self.database.close_user_db(&session.user_id)?;
         let _ = self.set_session(None)?;
         let _ = self.sign_out_on_server(&session.token).await?;
@@ -113,7 +113,7 @@ impl UserSession {
     pub async fn update_user(&self, params: UpdateUserParams) -> Result<(), UserError> {
         let session = self.get_session()?;
         let changeset = UserTableChangeset::new(params.clone());
-        diesel_update_table!(user_table, changeset, &*self.db_conn()?);
+        diesel_update_table!(user_table, changeset, &*self.db_connection()?);
 
         let _ = self.update_user_on_server(&session.token, params).await?;
         Ok(())
@@ -123,7 +123,7 @@ impl UserSession {
         let (user_id, token) = self.get_session()?.into_part();
         let user = dsl::user_table
             .filter(user_table::id.eq(&user_id))
-            .first::<UserTable>(&*(self.db_conn()?))?;
+            .first::<UserTable>(&*(self.db_connection()?))?;
 
         let _ = self.read_user_profile_on_server(&token).await?;
         Ok(UserProfile::from(user))
@@ -187,7 +187,7 @@ impl UserSession {
     }
 
     async fn save_user(&self, user: UserTable) -> Result<UserTable, UserError> {
-        let conn = self.db_conn()?;
+        let conn = self.db_connection()?;
         let _ = diesel::insert_into(user_table::table).values(user.clone()).execute(&*conn)?;
         Ok(user)
     }
@@ -236,7 +236,7 @@ pub async fn update_user(_server: Server, pool: Arc<ConnectionPool>, params: Upd
 }
 
 impl UserDatabaseConnection for UserSession {
-    fn get_connection(&self) -> Result<DBConnection, String> { self.db_conn().map_err(|e| format!("{:?}", e)) }
+    fn get_connection(&self) -> Result<DBConnection, String> { self.db_connection().map_err(|e| format!("{:?}", e)) }
 }
 
 const SESSION_CACHE_KEY: &str = "session_cache_key";

+ 3 - 4
rust-lib/flowy-workspace/src/entities/view/parser/view_thumbnail.rs

@@ -3,10 +3,9 @@ pub struct ViewThumbnail(pub String);
 
 impl ViewThumbnail {
     pub fn parse(s: String) -> Result<ViewThumbnail, String> {
-        if s.trim().is_empty() {
-            return Err(format!("View thumbnail can not be empty or whitespace"));
-        }
-
+        // if s.trim().is_empty() {
+        //     return Err(format!("View thumbnail can not be empty or whitespace"));
+        // }
         // TODO: verify the thumbnail url is valid or not
 
         Ok(Self(s))

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

@@ -95,7 +95,7 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
             desc: self.desc,
             thumbnail,
             view_type: self.view_type,
-            data: "".to_owned(),
+            data: "[{\"insert\":\"\\n\"}]".to_owned(),
         })
     }
 }

+ 15 - 11
rust-lib/flowy-workspace/src/module.rs

@@ -1,17 +1,13 @@
-use flowy_dispatch::prelude::*;
-
 use crate::{
-    errors::WorkspaceError,
+    errors::{ErrorBuilder, ErrorCode, WorkspaceError},
     event::WorkspaceEvent,
-    services::{AppController, WorkspaceController},
-};
-use flowy_database::DBConnection;
-
-use crate::{
     handlers::*,
-    services::{server::construct_workspace_server, ViewController},
+    services::{server::construct_workspace_server, AppController, ViewController, WorkspaceController},
 };
+use flowy_database::DBConnection;
+use flowy_dispatch::prelude::*;
 use flowy_document::module::Document;
+use flowy_sqlite::ConnectionPool;
 use std::sync::Arc;
 
 pub trait WorkspaceDeps: WorkspaceUser + WorkspaceDatabase {}
@@ -22,19 +18,27 @@ pub trait WorkspaceUser: Send + Sync {
 }
 
 pub trait WorkspaceDatabase: Send + Sync {
-    fn db_connection(&self) -> Result<DBConnection, WorkspaceError>;
+    fn db_pool(&self) -> Result<Arc<ConnectionPool>, WorkspaceError>;
+
+    fn db_connection(&self) -> Result<DBConnection, WorkspaceError> {
+        let pool = self.db_pool()?;
+        let conn = pool
+            .get()
+            .map_err(|e| ErrorBuilder::new(ErrorCode::InternalError).error(e).build())?;
+        Ok(conn)
+    }
 }
 
 pub fn create(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>, document: Arc<Document>) -> Module {
     let server = construct_workspace_server();
     let view_controller = Arc::new(ViewController::new(user.clone(), database.clone(), server.clone(), document));
-
     let app_controller = Arc::new(AppController::new(user.clone(), database.clone(), server.clone()));
 
     let workspace_controller = Arc::new(WorkspaceController::new(
         user.clone(),
         database.clone(),
         app_controller.clone(),
+        view_controller.clone(),
         server.clone(),
     ));
 

+ 7 - 2
rust-lib/flowy-workspace/src/services/app_controller.rs

@@ -31,11 +31,10 @@ impl AppController {
     #[tracing::instrument(level = "debug", skip(self), err)]
     pub(crate) async fn create_app(&self, params: CreateAppParams) -> Result<App, WorkspaceError> {
         let app = self.create_app_on_server(params).await?;
-        let app_table = AppTable::new(app.clone());
         let conn = &*self.database.db_connection()?;
 
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-            let _ = self.sql.create_app(app_table, &*conn)?;
+            let _ = self.save_app(app.clone(), &*conn)?;
             let apps = self.read_local_apps(&app.workspace_id, &*conn)?;
             notify(&app.workspace_id, WorkspaceObservable::WorkspaceCreateApp)
                 .payload(apps)
@@ -46,6 +45,12 @@ impl AppController {
         Ok(app)
     }
 
+    pub(crate) fn save_app(&self, app: App, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
+        let app_table = AppTable::new(app.clone());
+        let _ = self.sql.create_app(app_table, &*conn)?;
+        Ok(())
+    }
+
     pub(crate) async fn read_app(&self, params: QueryAppParams) -> Result<App, WorkspaceError> {
         let app_table = self
             .sql

+ 8 - 5
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -42,11 +42,9 @@ impl ViewController {
     pub(crate) async fn create_view(&self, params: CreateViewParams) -> Result<View, WorkspaceError> {
         let view = self.create_view_on_server(params.clone()).await?;
         let conn = &*self.database.db_connection()?;
-        let view_table = ViewTable::new(view.clone());
-
         // TODO: rollback anything created before if failed?
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-            let _ = self.sql.create_view(view_table, conn)?;
+            let _ = self.save_view(view.clone(), conn)?;
             self.document.doc.create(CreateDocParams::new(&view.id, &params.data), conn)?;
 
             let repeated_view = self.read_local_views_belong_to(&view.belong_to_id, conn)?;
@@ -59,6 +57,12 @@ impl ViewController {
         Ok(view)
     }
 
+    pub(crate) fn save_view(&self, view: View, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
+        let view_table = ViewTable::new(view);
+        let _ = self.sql.create_view(view_table, conn)?;
+        Ok(())
+    }
+
     pub(crate) async fn read_view(&self, params: QueryViewParams) -> Result<View, WorkspaceError> {
         let conn = self.database.db_connection()?;
         let view_table = self.sql.read_view(&params.view_id, Some(params.is_trash), &*conn)?;
@@ -68,8 +72,7 @@ impl ViewController {
     }
 
     pub(crate) async fn open_view(&self, params: QueryDocParams) -> Result<Doc, WorkspaceError> {
-        let conn = self.database.db_connection()?;
-        let doc = self.document.doc.open(params, &*conn)?;
+        let doc = self.document.doc.open(params, self.database.db_pool()?).await?;
         Ok(doc)
     }
 

+ 9 - 16
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -6,12 +6,8 @@ use crate::{
     errors::*,
     module::{WorkspaceDatabase, WorkspaceUser},
     observable::*,
-    services::{helper::spawn, server::Server, AppController},
-    sql_tables::{
-        app::{AppTable, AppTableSql},
-        view::{ViewTable, ViewTableSql},
-        workspace::{WorkspaceTable, WorkspaceTableChangeset, WorkspaceTableSql},
-    },
+    services::{helper::spawn, server::Server, AppController, ViewController},
+    sql_tables::workspace::{WorkspaceTable, WorkspaceTableChangeset, WorkspaceTableSql},
 };
 use flowy_database::SqliteConnection;
 use flowy_infra::kv::KV;
@@ -20,8 +16,7 @@ use std::sync::Arc;
 pub(crate) struct WorkspaceController {
     pub user: Arc<dyn WorkspaceUser>,
     pub workspace_sql: Arc<WorkspaceTableSql>,
-    pub app_sql: Arc<AppTableSql>,
-    pub view_sql: Arc<ViewTableSql>,
+    pub view_controller: Arc<ViewController>,
     pub database: Arc<dyn WorkspaceDatabase>,
     pub app_controller: Arc<AppController>,
     server: Server,
@@ -32,18 +27,16 @@ impl WorkspaceController {
         user: Arc<dyn WorkspaceUser>,
         database: Arc<dyn WorkspaceDatabase>,
         app_controller: Arc<AppController>,
+        view_controller: Arc<ViewController>,
         server: Server,
     ) -> Self {
         let workspace_sql = Arc::new(WorkspaceTableSql {});
-        let app_sql = Arc::new(AppTableSql {});
-        let view_sql = Arc::new(ViewTableSql {});
         Self {
             user,
             workspace_sql,
-            app_sql,
-            view_sql,
             database,
             app_controller,
+            view_controller,
             server,
         }
     }
@@ -253,8 +246,8 @@ impl WorkspaceController {
     fn read_workspaces_on_server(&self, user_id: String, params: QueryWorkspaceParams) -> Result<(), WorkspaceError> {
         let (token, server) = self.token_with_server()?;
         let workspace_sql = self.workspace_sql.clone();
-        let app_sql = self.app_sql.clone();
-        let view_sql = self.view_sql.clone();
+        let app_ctrl = self.app_controller.clone();
+        let view_ctrl = self.view_controller.clone();
         let conn = self.database.db_connection()?;
         spawn(async move {
             // Opti: handle the error and retry?
@@ -270,14 +263,14 @@ impl WorkspaceController {
                     log::debug!("Save {} apps", apps.len());
                     for mut app in apps {
                         let views = app.belongings.take_items();
-                        match app_sql.create_app(AppTable::new(app), &*conn) {
+                        match app_ctrl.save_app(app, &*conn) {
                             Ok(_) => {},
                             Err(e) => log::error!("create app failed: {:?}", e),
                         }
 
                         log::debug!("Save {} views", views.len());
                         for view in views {
-                            match view_sql.create_view(ViewTable::new(view), &*conn) {
+                            match view_ctrl.save_view(view, &*conn) {
                                 Ok(_) => {},
                                 Err(e) => log::error!("create view failed: {:?}", e),
                             }

+ 0 - 1
rust-lib/flowy-workspace/tests/workspace/app_test.rs

@@ -1,5 +1,4 @@
 use crate::helper::*;
-use flowy_test::prelude::*;
 
 use flowy_workspace::entities::{app::QueryAppRequest, view::*};
 

+ 0 - 1
rust-lib/flowy-workspace/tests/workspace/view_test.rs

@@ -1,6 +1,5 @@
 use crate::helper::*;
 
-use flowy_test::{FlowyEnv, FlowyTestSDK};
 use flowy_workspace::entities::view::*;
 
 #[test]

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

@@ -1,7 +1,7 @@
 use crate::helper::*;
 use flowy_test::{builder::*, FlowyEnv};
 use flowy_workspace::{
-    entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, RepeatedWorkspace},
+    entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest},
     event::WorkspaceEvent::*,
     prelude::*,
 };