Преглед на файлове

feat: integrate postgres storage (#2604)

* chore: env config

* chore: get user workspace

* feat: enable postgres storage

* chore: add new env

* chore: add set env ffi

* chore: pass env before backend init

* chore: update

* fix: ci tests

* chore: commit the generate env file

* chore: remove unused import
Nathan.fooo преди 2 години
родител
ревизия
056e2d49d0
променени са 87 файла, в които са добавени 1401 реда и са изтрити 1111 реда
  1. 19 0
      frontend/appflowy_flutter/lib/core/config/config.dart
  2. 7 0
      frontend/appflowy_flutter/lib/env/env.dart
  3. 1 2
      frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart
  4. 1 0
      frontend/appflowy_flutter/lib/startup/startup.dart
  5. 27 0
      frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
  6. 3 0
      frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart
  7. 2 0
      frontend/appflowy_flutter/linux/flutter/dart_ffi/binding.h
  8. 7 0
      frontend/appflowy_flutter/packages/appflowy_backend/lib/appflowy_backend.dart
  9. 64 0
      frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.dart
  10. 65 0
      frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.l.dart
  11. 16 0
      frontend/appflowy_flutter/packages/appflowy_backend/lib/ffi.dart
  12. 2 0
      frontend/appflowy_flutter/packages/appflowy_backend/linux/Classes/binding.h
  13. 2 0
      frontend/appflowy_flutter/packages/appflowy_backend/macos/Classes/binding.h
  14. 2 0
      frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml
  15. 21 11
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  16. 6 6
      frontend/appflowy_tauri/src-tauri/Cargo.toml
  17. 36 54
      frontend/rust-lib/Cargo.lock
  18. 5 5
      frontend/rust-lib/Cargo.toml
  19. 1 1
      frontend/rust-lib/dart-ffi/Cargo.toml
  20. 2 0
      frontend/rust-lib/dart-ffi/binding.h
  21. 19 0
      frontend/rust-lib/dart-ffi/src/env_serde.rs
  22. 9 0
      frontend/rust-lib/dart-ffi/src/lib.rs
  23. 1 0
      frontend/rust-lib/flowy-config/Cargo.toml
  24. 81 13
      frontend/rust-lib/flowy-config/src/entities.rs
  25. 22 2
      frontend/rust-lib/flowy-config/src/event_handler.rs
  26. 7 0
      frontend/rust-lib/flowy-config/src/event_map.rs
  27. 1 3
      frontend/rust-lib/flowy-core/Cargo.toml
  28. 5 3
      frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs
  29. 110 17
      frontend/rust-lib/flowy-core/src/integrate/server.rs
  30. 22 33
      frontend/rust-lib/flowy-core/src/lib.rs
  31. 1 2
      frontend/rust-lib/flowy-database2/src/event_handler.rs
  32. 3 3
      frontend/rust-lib/flowy-database2/src/manager.rs
  33. 22 12
      frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs
  34. 1 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/format.rs
  35. 1 3
      frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs
  36. 1 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option_entities.rs
  37. 1 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs
  38. 1 1
      frontend/rust-lib/flowy-database2/src/services/sort/controller.rs
  39. 4 4
      frontend/rust-lib/flowy-database2/tests/database/database_editor.rs
  40. 0 425
      frontend/rust-lib/flowy-database2/tests/database/script.rs
  41. 3 3
      frontend/rust-lib/flowy-document2/tests/document/util.rs
  42. 17 0
      frontend/rust-lib/flowy-folder2/src/deps.rs
  43. 1 1
      frontend/rust-lib/flowy-folder2/src/entities/workspace.rs
  44. 1 0
      frontend/rust-lib/flowy-folder2/src/event_map.rs
  45. 2 0
      frontend/rust-lib/flowy-folder2/src/lib.rs
  46. 11 16
      frontend/rust-lib/flowy-folder2/src/manager.rs
  47. 4 3
      frontend/rust-lib/flowy-folder2/tests/workspace/folder_test.rs
  48. 26 26
      frontend/rust-lib/flowy-folder2/tests/workspace/script.rs
  49. 2 1
      frontend/rust-lib/flowy-server/Cargo.toml
  50. 16 0
      frontend/rust-lib/flowy-server/src/lib.rs
  51. 21 0
      frontend/rust-lib/flowy-server/src/local_server/impls/folder.rs
  52. 5 0
      frontend/rust-lib/flowy-server/src/local_server/impls/mod.rs
  53. 0 0
      frontend/rust-lib/flowy-server/src/local_server/impls/user.rs
  54. 1 1
      frontend/rust-lib/flowy-server/src/local_server/mod.rs
  55. 8 1
      frontend/rust-lib/flowy-server/src/local_server/server.rs
  56. 21 0
      frontend/rust-lib/flowy-server/src/self_host/impls/folder.rs
  57. 5 0
      frontend/rust-lib/flowy-server/src/self_host/impls/mod.rs
  58. 0 0
      frontend/rust-lib/flowy-server/src/self_host/impls/user.rs
  59. 1 2
      frontend/rust-lib/flowy-server/src/self_host/mod.rs
  60. 8 1
      frontend/rust-lib/flowy-server/src/self_host/server.rs
  61. 54 0
      frontend/rust-lib/flowy-server/src/supabase/impls/folder.rs
  62. 5 0
      frontend/rust-lib/flowy-server/src/supabase/impls/mod.rs
  63. 27 27
      frontend/rust-lib/flowy-server/src/supabase/impls/user.rs
  64. 1 1
      frontend/rust-lib/flowy-server/src/supabase/mod.rs
  65. 87 38
      frontend/rust-lib/flowy-server/src/supabase/request.rs
  66. 17 4
      frontend/rust-lib/flowy-server/src/supabase/response.rs
  67. 24 3
      frontend/rust-lib/flowy-server/src/supabase/server.rs
  68. 4 8
      frontend/rust-lib/flowy-test/Cargo.toml
  69. 15 45
      frontend/rust-lib/flowy-test/src/event_builder.rs
  70. 111 0
      frontend/rust-lib/flowy-test/src/folder_event.rs
  71. 0 216
      frontend/rust-lib/flowy-test/src/helper.rs
  72. 37 32
      frontend/rust-lib/flowy-test/src/lib.rs
  73. 103 0
      frontend/rust-lib/flowy-test/src/user_event.rs
  74. 1 0
      frontend/rust-lib/flowy-test/tests/main.rs
  75. 33 27
      frontend/rust-lib/flowy-test/tests/user/local_test/auth_test.rs
  76. 0 5
      frontend/rust-lib/flowy-test/tests/user/local_test/helper.rs
  77. 0 0
      frontend/rust-lib/flowy-test/tests/user/local_test/mod.rs
  78. 28 40
      frontend/rust-lib/flowy-test/tests/user/local_test/user_profile_test.rs
  79. 2 0
      frontend/rust-lib/flowy-test/tests/user/mod.rs
  80. 28 0
      frontend/rust-lib/flowy-test/tests/user/supabase_test/auth_test.rs
  81. 20 0
      frontend/rust-lib/flowy-test/tests/user/supabase_test/helper.rs
  82. 3 0
      frontend/rust-lib/flowy-test/tests/user/supabase_test/mod.rs
  83. 38 0
      frontend/rust-lib/flowy-test/tests/user/supabase_test/workspace_test.rs
  84. 0 1
      frontend/rust-lib/flowy-user/Cargo.toml
  85. 1 0
      frontend/rust-lib/flowy-user/src/event_handler.rs
  86. 5 0
      frontend/rust-lib/flowy-user/src/event_map.rs
  87. 3 0
      frontend/rust-lib/flowy-user/src/services/user_session.rs

+ 19 - 0
frontend/appflowy_flutter/lib/core/config/config.dart

@@ -16,4 +16,23 @@ class Config {
         ..jwtSecret = secret,
     ).send();
   }
+
+  static Future<void> setSupabaseCollabPluginConfig({
+    required String url,
+    required String key,
+    required String jwtSecret,
+    required String collabTable,
+  }) async {
+    final payload = CollabPluginConfigPB.create();
+    final collabTableConfig = CollabTableConfigPB.create()
+      ..tableName = collabTable;
+
+    payload.supabaseConfig = SupabaseDBConfigPB.create()
+      ..supabaseUrl = url
+      ..key = key
+      ..jwtSecret = jwtSecret
+      ..collabTableConfig = collabTableConfig;
+
+    await ConfigEventSetCollabPluginConfig(payload).send();
+  }
 }

+ 7 - 0
frontend/appflowy_flutter/lib/env/env.dart

@@ -29,6 +29,13 @@ abstract class Env {
     defaultValue: '',
   )
   static final supabaseJwtSecret = _Env.supabaseJwtSecret;
+
+  @EnviedField(
+    obfuscate: true,
+    varName: 'SUPABASE_COLLAB_TABLE',
+    defaultValue: '',
+  )
+  static final supabaseCollabTable = _Env.supabaseCollabTable;
 }
 
 bool get isSupabaseEnable =>

+ 1 - 2
frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart

@@ -6,7 +6,6 @@ import 'package:appflowy/util/json_print.dart';
 import 'package:appflowy/workspace/application/view/view_listener.dart';
 import 'package:appflowy/workspace/application/doc/doc_listener.dart';
 import 'package:appflowy/plugins/document/application/doc_service.dart';
-import 'package:appflowy_backend/log.dart';
 import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
 import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
 import 'package:appflowy_editor/appflowy_editor.dart'
@@ -153,7 +152,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
       editorState.logConfiguration
         ..level = LogLevel.all
         ..handler = (log) {
-          Log.debug(log);
+          // Log.debug(log);
         };
     }
   }

+ 1 - 0
frontend/appflowy_flutter/lib/startup/startup.dart

@@ -62,6 +62,7 @@ class FlowyRunner {
             anonKey: Env.supabaseAnonKey,
             key: Env.supabaseKey,
             jwtSecret: Env.supabaseJwtSecret,
+            collabTable: Env.supabaseCollabTable,
           ),
           const InitAppWidgetTask(),
           const InitPlatformServiceTask()

+ 27 - 0
frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart

@@ -1,6 +1,8 @@
 import 'dart:io';
 
+import 'package:appflowy/env/env.dart';
 import 'package:appflowy_backend/appflowy_backend.dart';
+import 'package:appflowy_backend/env_serde.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:path/path.dart' as path;
 
@@ -20,10 +22,35 @@ class InitRustSDKTask extends LaunchTask {
   @override
   Future<void> initialize(LaunchContext context) async {
     final dir = directory ?? await appFlowyDocumentDirectory();
+
+    context.getIt<FlowySDK>().setEnv(getAppFlowyEnv());
     await context.getIt<FlowySDK>().init(dir);
   }
 }
 
+AppFlowyEnv getAppFlowyEnv() {
+  final supabaseConfig = SupabaseConfiguration(
+    url: Env.supabaseUrl,
+    key: Env.supabaseKey,
+    jwt_secret: Env.supabaseJwtSecret,
+  );
+
+  final collabTableConfig =
+      CollabTableConfig(enable: true, table_name: Env.supabaseCollabTable);
+
+  final supbaseDBConfig = SupabaseDBConfig(
+    url: Env.supabaseUrl,
+    key: Env.supabaseKey,
+    jwt_secret: Env.supabaseJwtSecret,
+    collab_table_config: collabTableConfig,
+  );
+
+  return AppFlowyEnv(
+    supabase_config: supabaseConfig,
+    supabase_db_config: supbaseDBConfig,
+  );
+}
+
 Future<Directory> appFlowyDocumentDirectory() async {
   switch (integrationEnv()) {
     case IntegrationMode.develop:

+ 3 - 0
frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart

@@ -13,12 +13,14 @@ class InitSupabaseTask extends LaunchTask {
     required this.anonKey,
     required this.key,
     required this.jwtSecret,
+    this.collabTable = "",
   });
 
   final String url;
   final String anonKey;
   final String key;
   final String jwtSecret;
+  final String collabTable;
 
   @override
   Future<void> initialize(LaunchContext context) async {
@@ -33,6 +35,7 @@ class InitSupabaseTask extends LaunchTask {
     await Supabase.initialize(
       url: url,
       anonKey: anonKey,
+      debug: false,
     );
     await Config.setSupabaseConfig(
       url: url,

+ 2 - 0
frontend/appflowy_flutter/linux/flutter/dart_ffi/binding.h

@@ -14,3 +14,5 @@ int32_t set_stream_port(int64_t port);
 void link_me_please(void);
 
 void backend_log(int64_t level, const char *data);
+
+void set_env(const char *data);

+ 7 - 0
frontend/appflowy_flutter/packages/appflowy_backend/lib/appflowy_backend.dart

@@ -1,9 +1,11 @@
 export 'package:async/async.dart';
+import 'dart:convert';
 import 'dart:io';
 import 'dart:async';
 import 'package:appflowy_backend/rust_stream.dart';
 import 'package:flutter/services.dart';
 import 'dart:ffi';
+import 'env_serde.dart';
 import 'ffi.dart' as ffi;
 import 'package:ffi/ffi.dart';
 
@@ -34,4 +36,9 @@ class FlowySDK {
     ffi.store_dart_post_cobject(NativeApi.postCObject);
     ffi.init_sdk(sdkDir.path.toNativeUtf8());
   }
+
+  void setEnv(AppFlowyEnv env) {
+    final jsonStr = jsonEncode(env.toJson());
+    ffi.set_env(jsonStr.toNativeUtf8());
+  }
 }

+ 64 - 0
frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.dart

@@ -0,0 +1,64 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'env_serde.l.dart';
+
+@JsonSerializable()
+class AppFlowyEnv {
+  final SupabaseConfiguration supabase_config;
+  final SupabaseDBConfig supabase_db_config;
+
+  AppFlowyEnv(
+      {required this.supabase_config, required this.supabase_db_config});
+
+  factory AppFlowyEnv.fromJson(Map<String, dynamic> json) =>
+      _$AppFlowyEnvFromJson(json);
+
+  Map<String, dynamic> toJson() => _$AppFlowyEnvToJson(this);
+}
+
+@JsonSerializable()
+class SupabaseConfiguration {
+  final String url;
+  final String key;
+  final String jwt_secret;
+
+  SupabaseConfiguration(
+      {required this.url, required this.key, required this.jwt_secret});
+
+  factory SupabaseConfiguration.fromJson(Map<String, dynamic> json) =>
+      _$SupabaseConfigurationFromJson(json);
+
+  Map<String, dynamic> toJson() => _$SupabaseConfigurationToJson(this);
+}
+
+@JsonSerializable()
+class SupabaseDBConfig {
+  final String url;
+  final String key;
+  final String jwt_secret;
+  final CollabTableConfig collab_table_config;
+
+  SupabaseDBConfig(
+      {required this.url,
+      required this.key,
+      required this.jwt_secret,
+      required this.collab_table_config});
+
+  factory SupabaseDBConfig.fromJson(Map<String, dynamic> json) =>
+      _$SupabaseDBConfigFromJson(json);
+
+  Map<String, dynamic> toJson() => _$SupabaseDBConfigToJson(this);
+}
+
+@JsonSerializable()
+class CollabTableConfig {
+  final String table_name;
+  final bool enable;
+
+  CollabTableConfig({required this.table_name, required this.enable});
+
+  factory CollabTableConfig.fromJson(Map<String, dynamic> json) =>
+      _$CollabTableConfigFromJson(json);
+
+  Map<String, dynamic> toJson() => _$CollabTableConfigToJson(this);
+}

+ 65 - 0
frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.l.dart

@@ -0,0 +1,65 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'env_serde.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+AppFlowyEnv _$AppFlowyEnvFromJson(Map<String, dynamic> json) => AppFlowyEnv(
+      supabase_config: SupabaseConfiguration.fromJson(
+          json['supabase_config'] as Map<String, dynamic>),
+      supabase_db_config: SupabaseDBConfig.fromJson(
+          json['supabase_db_config'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$AppFlowyEnvToJson(AppFlowyEnv instance) =>
+    <String, dynamic>{
+      'supabase_config': instance.supabase_config,
+      'supabase_db_config': instance.supabase_db_config,
+    };
+
+SupabaseConfiguration _$SupabaseConfigurationFromJson(
+        Map<String, dynamic> json) =>
+    SupabaseConfiguration(
+      url: json['url'] as String,
+      key: json['key'] as String,
+      jwt_secret: json['jwt_secret'] as String,
+    );
+
+Map<String, dynamic> _$SupabaseConfigurationToJson(
+        SupabaseConfiguration instance) =>
+    <String, dynamic>{
+      'url': instance.url,
+      'key': instance.key,
+      'jwt_secret': instance.jwt_secret,
+    };
+
+SupabaseDBConfig _$SupabaseDBConfigFromJson(Map<String, dynamic> json) =>
+    SupabaseDBConfig(
+      url: json['url'] as String,
+      key: json['key'] as String,
+      jwt_secret: json['jwt_secret'] as String,
+      collab_table_config: CollabTableConfig.fromJson(
+          json['collab_table_config'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$SupabaseDBConfigToJson(SupabaseDBConfig instance) =>
+    <String, dynamic>{
+      'url': instance.url,
+      'key': instance.key,
+      'jwt_secret': instance.jwt_secret,
+      'collab_table_config': instance.collab_table_config,
+    };
+
+CollabTableConfig _$CollabTableConfigFromJson(Map<String, dynamic> json) =>
+    CollabTableConfig(
+      table_name: json['table_name'] as String,
+      enable: json['enable'] as bool,
+    );
+
+Map<String, dynamic> _$CollabTableConfigToJson(CollabTableConfig instance) =>
+    <String, dynamic>{
+      'table_name': instance.table_name,
+      'enable': instance.enable,
+    };

+ 16 - 0
frontend/appflowy_flutter/packages/appflowy_backend/lib/ffi.dart

@@ -151,3 +151,19 @@ typedef _invoke_log_Dart = void Function(
   int level,
   Pointer<ffi.Utf8>,
 );
+
+/// C function `set_env`.
+void set_env(
+  Pointer<ffi.Utf8> data,
+) {
+  _set_env(data);
+}
+
+final _set_env_Dart _set_env =
+    _dart_ffi_lib.lookupFunction<_set_env_C, _set_env_Dart>('set_env');
+typedef _set_env_C = Void Function(
+  Pointer<ffi.Utf8> data,
+);
+typedef _set_env_Dart = void Function(
+  Pointer<ffi.Utf8> data,
+);

+ 2 - 0
frontend/appflowy_flutter/packages/appflowy_backend/linux/Classes/binding.h

@@ -15,3 +15,5 @@ int32_t set_stream_port(int64_t port);
 void link_me_please(void);
 
 void backend_log(int64_t level, const char *data);
+
+void set_env(const char *data);

+ 2 - 0
frontend/appflowy_flutter/packages/appflowy_backend/macos/Classes/binding.h

@@ -14,3 +14,5 @@ int32_t set_stream_port(int64_t port);
 void link_me_please(void);
 
 void backend_log(int64_t level, const char *data);
+
+void set_env(const char *data);

+ 2 - 0
frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml

@@ -18,6 +18,7 @@ dependencies:
   freezed_annotation:
   logger: ^1.0.0
   plugin_platform_interface: ^2.1.3
+  json_annotation: ^4.7.0
 
 dev_dependencies:
   flutter_test:
@@ -25,6 +26,7 @@ dev_dependencies:
   build_runner:
   freezed:
   flutter_lints: ^2.0.1
+  json_serializable: ^6.6.2
 
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec

+ 21 - 11
frontend/appflowy_tauri/src-tauri/Cargo.lock

@@ -99,8 +99,9 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
+ "anyhow",
  "collab",
  "collab-database",
  "collab-document",
@@ -109,6 +110,7 @@ dependencies = [
  "collab-plugins",
  "serde",
  "serde_json",
+ "tracing",
 ]
 
 [[package]]
@@ -1021,7 +1023,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "bytes",
@@ -1038,7 +1040,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -1056,7 +1058,7 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1075,12 +1077,13 @@ dependencies = [
  "thiserror",
  "tokio",
  "tracing",
+ "uuid",
 ]
 
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1092,7 +1095,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "collab",
@@ -1109,7 +1112,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "collab",
@@ -1127,7 +1130,7 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "bincode",
  "chrono",
@@ -1147,21 +1150,25 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "async-trait",
  "aws-config",
  "aws-credential-types",
  "aws-sdk-dynamodb",
+ "base64 0.21.0",
  "collab",
  "collab-client-ws",
  "collab-persistence",
  "collab-sync",
  "futures-util",
  "parking_lot 0.12.1",
+ "postgrest",
  "rand 0.8.5",
  "rusoto_credential",
+ "serde",
+ "serde_json",
  "thiserror",
  "tokio",
  "tokio-retry",
@@ -1173,7 +1180,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "bytes",
  "collab",
@@ -1773,6 +1780,7 @@ dependencies = [
  "flowy-codegen",
  "flowy-derive",
  "flowy-error",
+ "flowy-server",
  "flowy-sqlite",
  "lib-dispatch",
  "protobuf",
@@ -1803,6 +1811,7 @@ dependencies = [
  "parking_lot 0.12.1",
  "serde",
  "serde_json",
+ "serde_repr",
  "tokio",
  "tracing",
 ]
@@ -1972,9 +1981,10 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "bytes",
+ "chrono",
  "config",
- "flowy-config",
  "flowy-error",
+ "flowy-folder2",
  "flowy-user",
  "futures-util",
  "hyper",

+ 6 - 6
frontend/appflowy_tauri/src-tauri/Cargo.toml

@@ -34,12 +34,12 @@ default = ["custom-protocol"]
 custom-protocol = ["tauri/custom-protocol"]
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
-collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
 
 #collab = { path = "../../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

+ 36 - 54
frontend/rust-lib/Cargo.lock

@@ -85,8 +85,9 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
+ "anyhow",
  "collab",
  "collab-database",
  "collab-document",
@@ -95,6 +96,7 @@ dependencies = [
  "collab-plugins",
  "serde",
  "serde_json",
+ "tracing",
 ]
 
 [[package]]
@@ -884,7 +886,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "bytes",
@@ -901,7 +903,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -919,7 +921,7 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -938,12 +940,13 @@ dependencies = [
  "thiserror",
  "tokio",
  "tracing",
+ "uuid",
 ]
 
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -955,7 +958,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "collab",
@@ -972,7 +975,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "collab",
@@ -990,7 +993,7 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "bincode",
  "chrono",
@@ -1010,21 +1013,25 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "anyhow",
  "async-trait",
  "aws-config",
  "aws-credential-types",
  "aws-sdk-dynamodb",
+ "base64 0.21.0",
  "collab",
  "collab-client-ws",
  "collab-persistence",
  "collab-sync",
  "futures-util",
  "parking_lot 0.12.1",
+ "postgrest",
  "rand 0.8.5",
  "rusoto_credential",
+ "serde",
+ "serde_json",
  "thiserror",
  "tokio",
  "tokio-retry",
@@ -1036,7 +1043,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7a2e97#7a2e97d9bfe746f5db18753ab0b59347ec5bf23f"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bff164#bff1647076b2370c34f19d57d8d0da4bbb80ee55"
 dependencies = [
  "bytes",
  "collab",
@@ -1254,6 +1261,7 @@ name = "dart-ffi"
 version = "0.1.0"
 dependencies = [
  "allo-isolate",
+ "appflowy-integrate",
  "byteorder",
  "bytes",
  "crossbeam-utils",
@@ -1463,11 +1471,12 @@ dependencies = [
 
 [[package]]
 name = "fake"
-version = "2.5.0"
+version = "2.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d68f517805463f3a896a9d29c1d6ff09d3579ded64a7201b4069f8f9c0d52fd"
+checksum = "0a44c765350db469b774425ff1c833890b16ceb9612fb5d7c4bbdf4a1b55f876"
 dependencies = [
  "rand 0.8.5",
+ "unidecode",
 ]
 
 [[package]]
@@ -1551,6 +1560,7 @@ dependencies = [
  "flowy-codegen",
  "flowy-derive",
  "flowy-error",
+ "flowy-server",
  "flowy-sqlite",
  "lib-dispatch",
  "protobuf",
@@ -1582,6 +1592,7 @@ dependencies = [
  "parking_lot 0.12.1",
  "serde",
  "serde_json",
+ "serde_repr",
  "tokio",
  "tracing",
 ]
@@ -1757,10 +1768,11 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "bytes",
+ "chrono",
  "config",
  "dotenv",
- "flowy-config",
  "flowy-error",
+ "flowy-folder2",
  "flowy-user",
  "futures-util",
  "hyper",
@@ -1820,28 +1832,26 @@ name = "flowy-test"
 version = "0.1.0"
 dependencies = [
  "bytes",
- "fake",
+ "dotenv",
  "flowy-core",
  "flowy-folder2",
  "flowy-net",
  "flowy-server",
  "flowy-user",
- "futures",
  "futures-util",
  "lib-dispatch",
  "lib-infra",
  "lib-ot",
- "log",
  "nanoid",
+ "parking_lot 0.12.1",
  "protobuf",
- "quickcheck",
- "quickcheck_macros 0.9.1",
  "serde",
  "serde_json",
- "serial_test",
  "tempdir",
  "thread-id",
  "tokio",
+ "tracing",
+ "uuid",
 ]
 
 [[package]]
@@ -1859,7 +1869,6 @@ dependencies = [
  "flowy-error",
  "flowy-notification",
  "flowy-sqlite",
- "flowy-test",
  "lazy_static",
  "lib-dispatch",
  "lib-infra",
@@ -1869,7 +1878,7 @@ dependencies = [
  "parking_lot 0.12.1",
  "protobuf",
  "quickcheck",
- "quickcheck_macros 1.0.0",
+ "quickcheck_macros",
  "rand 0.8.5",
  "rand_core 0.6.4",
  "serde",
@@ -3483,17 +3492,6 @@ dependencies = [
  "rand 0.8.5",
 ]
 
-[[package]]
-name = "quickcheck_macros"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "quickcheck_macros"
 version = "1.0.0"
@@ -4121,28 +4119,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "serial_test"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d"
-dependencies = [
- "lazy_static",
- "parking_lot 0.11.2",
- "serial_test_derive",
-]
-
-[[package]]
-name = "serial_test_derive"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "sha-1"
 version = "0.9.8"
@@ -4978,6 +4954,12 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
+[[package]]
+name = "unidecode"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
+
 [[package]]
 name = "untrusted"
 version = "0.7.1"

+ 5 - 5
frontend/rust-lib/Cargo.toml

@@ -33,11 +33,11 @@ opt-level = 3
 incremental = false
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97"  }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97"  }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7a2e97" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164"  }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164"  }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bff164" }
 
 #collab = { path = "../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }

+ 1 - 1
frontend/rust-lib/dart-ffi/Cargo.toml

@@ -24,7 +24,7 @@ crossbeam-utils = "0.8.15"
 lazy_static = "1.4.0"
 parking_lot = "0.12.1"
 tracing = { version = "0.1", features = ["log"] }
-
+appflowy-integrate = {version = "0.1.0" }
 
 lib-dispatch = { path = "../lib-dispatch" }
 flowy-core = { path = "../flowy-core" }

+ 2 - 0
frontend/rust-lib/dart-ffi/binding.h

@@ -14,3 +14,5 @@ int32_t set_stream_port(int64_t port);
 void link_me_please(void);
 
 void backend_log(int64_t level, const char *data);
+
+void set_env(const char *data);

+ 19 - 0
frontend/rust-lib/dart-ffi/src/env_serde.rs

@@ -0,0 +1,19 @@
+use appflowy_integrate::SupabaseDBConfig;
+use flowy_server::supabase::SupabaseConfiguration;
+use serde::Deserialize;
+
+#[derive(Deserialize, Debug)]
+pub struct AppFlowyEnv {
+  supabase_config: SupabaseConfiguration,
+  supabase_db_config: SupabaseDBConfig,
+}
+
+impl AppFlowyEnv {
+  pub fn parser(env_str: &str) {
+    if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) {
+      dbg!(&env);
+      env.supabase_config.write_env();
+      env.supabase_db_config.write_env();
+    }
+  }
+}

+ 9 - 0
frontend/rust-lib/dart-ffi/src/lib.rs

@@ -10,6 +10,7 @@ use flowy_notification::register_notification_sender;
 use lib_dispatch::prelude::ToBytes;
 use lib_dispatch::prelude::*;
 
+use crate::env_serde::AppFlowyEnv;
 use crate::notification::DartNotificationSender;
 use crate::{
   c::{extend_front_four_bytes_into_bytes, forget_rust},
@@ -17,6 +18,7 @@ use crate::{
 };
 
 mod c;
+mod env_serde;
 mod model;
 mod notification;
 mod protobuf;
@@ -134,3 +136,10 @@ pub extern "C" fn backend_log(level: i64, data: *const c_char) {
     _ => (),
   }
 }
+
+#[no_mangle]
+pub extern "C" fn set_env(data: *const c_char) {
+  let c_str = unsafe { CStr::from_ptr(data) };
+  let serde_str = c_str.to_str().unwrap();
+  AppFlowyEnv::parser(serde_str);
+}

+ 1 - 0
frontend/rust-lib/flowy-config/Cargo.toml

@@ -14,6 +14,7 @@ bytes = { version = "1.4" }
 flowy-error = { path = "../flowy-error" }
 strum_macros = "0.21"
 appflowy-integrate = {version = "0.1.0" }
+flowy-server = { path = "../flowy-server" }
 
 [build-dependencies]
 flowy-codegen = { path = "../../../shared-lib/flowy-codegen"}

+ 81 - 13
frontend/rust-lib/flowy-config/src/entities.rs

@@ -1,4 +1,8 @@
+use appflowy_integrate::config::AWSDynamoDBConfig;
+use appflowy_integrate::{CollabTableConfig, SupabaseDBConfig};
 use flowy_derive::ProtoBuf;
+use flowy_error::FlowyError;
+use flowy_server::supabase::SupabaseConfiguration;
 
 #[derive(Default, ProtoBuf)]
 pub struct KeyValuePB {
@@ -15,11 +19,6 @@ pub struct KeyPB {
   pub key: String,
 }
 
-pub const SUPABASE_URL: &str = "SUPABASE_URL";
-pub const SUPABASE_ANON_KEY: &str = "SUPABASE_ANON_KEY";
-pub const SUPABASE_KEY: &str = "SUPABASE_KEY";
-pub const SUPABASE_JWT_SECRET: &str = "SUPABASE_JWT_SECRET";
-
 #[derive(Default, ProtoBuf)]
 pub struct SupabaseConfigPB {
   #[pb(index = 1)]
@@ -35,28 +34,97 @@ pub struct SupabaseConfigPB {
   jwt_secret: String,
 }
 
-impl SupabaseConfigPB {
-  pub(crate) fn write_to_env(self) {
-    std::env::set_var(SUPABASE_URL, self.supabase_url);
-    std::env::set_var(SUPABASE_ANON_KEY, self.anon_key);
-    std::env::set_var(SUPABASE_KEY, self.key);
-    std::env::set_var(SUPABASE_JWT_SECRET, self.jwt_secret);
+impl TryFrom<SupabaseConfigPB> for SupabaseConfiguration {
+  type Error = FlowyError;
+
+  fn try_from(value: SupabaseConfigPB) -> Result<Self, Self::Error> {
+    Ok(Self {
+      url: value.supabase_url,
+      key: value.key,
+      jwt_secret: value.jwt_secret,
+    })
   }
 }
 
 #[derive(Default, ProtoBuf)]
-pub struct AppFlowyCollabConfigPB {
+pub struct CollabPluginConfigPB {
   #[pb(index = 1, one_of)]
-  aws_config: Option<AWSDynamoDBConfigPB>,
+  pub aws_config: Option<AWSDynamoDBConfigPB>,
+
+  #[pb(index = 2, one_of)]
+  pub supabase_config: Option<SupabaseDBConfigPB>,
 }
 
 #[derive(Default, ProtoBuf)]
 pub struct AWSDynamoDBConfigPB {
   #[pb(index = 1)]
   pub access_key_id: String,
+
   #[pb(index = 2)]
   pub secret_access_key: String,
   // Region list: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
   #[pb(index = 3)]
   pub region: String,
 }
+
+impl TryFrom<AWSDynamoDBConfigPB> for AWSDynamoDBConfig {
+  type Error = FlowyError;
+
+  fn try_from(config: AWSDynamoDBConfigPB) -> Result<Self, Self::Error> {
+    Ok(AWSDynamoDBConfig {
+      access_key_id: config.access_key_id,
+      secret_access_key: config.secret_access_key,
+      region: config.region,
+      enable: true,
+    })
+  }
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct SupabaseDBConfigPB {
+  #[pb(index = 1)]
+  pub supabase_url: String,
+
+  #[pb(index = 2)]
+  pub key: String,
+
+  #[pb(index = 3)]
+  pub jwt_secret: String,
+
+  #[pb(index = 4)]
+  pub collab_table_config: CollabTableConfigPB,
+}
+
+impl TryFrom<SupabaseDBConfigPB> for SupabaseDBConfig {
+  type Error = FlowyError;
+
+  fn try_from(config: SupabaseDBConfigPB) -> Result<Self, Self::Error> {
+    let update_table_config = CollabTableConfig::try_from(config.collab_table_config)?;
+    Ok(SupabaseDBConfig {
+      url: config.supabase_url,
+      key: config.key,
+      jwt_secret: config.jwt_secret,
+      collab_table_config: update_table_config,
+    })
+  }
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct CollabTableConfigPB {
+  #[pb(index = 1)]
+  pub table_name: String,
+}
+
+impl TryFrom<CollabTableConfigPB> for CollabTableConfig {
+  type Error = FlowyError;
+
+  fn try_from(config: CollabTableConfigPB) -> Result<Self, Self::Error> {
+    if config.table_name.is_empty() {
+      return Err(FlowyError::internal().context("table_name is empty"));
+    }
+    Ok(CollabTableConfig {
+      table_name: config.table_name,
+      enable: true,
+    })
+  }
+}

+ 22 - 2
frontend/rust-lib/flowy-config/src/event_handler.rs

@@ -1,8 +1,11 @@
+use appflowy_integrate::config::AWSDynamoDBConfig;
+use appflowy_integrate::SupabaseDBConfig;
 use flowy_error::{FlowyError, FlowyResult};
+use flowy_server::supabase::SupabaseConfiguration;
 use flowy_sqlite::kv::KV;
 use lib_dispatch::prelude::{data_result_ok, AFPluginData, DataResult};
 
-use crate::entities::{KeyPB, KeyValuePB, SupabaseConfigPB};
+use crate::entities::{CollabPluginConfigPB, KeyPB, KeyValuePB, SupabaseConfigPB};
 
 pub(crate) async fn set_key_value_handler(data: AFPluginData<KeyValuePB>) -> FlowyResult<()> {
   let data = data.into_inner();
@@ -34,8 +37,25 @@ pub(crate) async fn remove_key_value_handler(data: AFPluginData<KeyPB>) -> Flowy
 
 pub(crate) async fn set_supabase_config_handler(
   data: AFPluginData<SupabaseConfigPB>,
+) -> FlowyResult<()> {
+  let config = SupabaseConfiguration::try_from(data.into_inner())?;
+  config.write_env();
+  Ok(())
+}
+
+pub(crate) async fn set_collab_plugin_config_handler(
+  data: AFPluginData<CollabPluginConfigPB>,
 ) -> FlowyResult<()> {
   let config = data.into_inner();
-  config.write_to_env();
+  if let Some(aws_config_pb) = config.aws_config {
+    if let Ok(aws_config) = AWSDynamoDBConfig::try_from(aws_config_pb) {
+      aws_config.write_env();
+    }
+  }
+  if let Some(supabase_config_pb) = config.supabase_config {
+    if let Ok(supabase_config) = SupabaseDBConfig::try_from(supabase_config_pb) {
+      supabase_config.write_env();
+    }
+  }
   Ok(())
 }

+ 7 - 0
frontend/rust-lib/flowy-config/src/event_map.rs

@@ -12,6 +12,10 @@ pub fn init() -> AFPlugin {
     .event(ConfigEvent::GetKeyValue, get_key_value_handler)
     .event(ConfigEvent::RemoveKeyValue, remove_key_value_handler)
     .event(ConfigEvent::SetSupabaseConfig, set_supabase_config_handler)
+    .event(
+      ConfigEvent::SetCollabPluginConfig,
+      set_collab_plugin_config_handler,
+    )
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Display, ProtoBuf_Enum, Flowy_Event)]
@@ -30,4 +34,7 @@ pub enum ConfigEvent {
   /// Check out the `write_to_env` of [SupabaseConfigPB].
   #[event(input = "SupabaseConfigPB")]
   SetSupabaseConfig = 3,
+
+  #[event(input = "CollabPluginConfigPB")]
+  SetCollabPluginConfig = 4,
 }

+ 1 - 3
frontend/rust-lib/flowy-core/Cargo.toml

@@ -11,12 +11,9 @@ lib-log = { path = "../lib-log" }
 flowy-user = { path = "../flowy-user" }
 flowy-net = { path = "../flowy-net" }
 flowy-folder2 = { path = "../flowy-folder2" }
-#flowy-database = { path = "../flowy-database" }
 flowy-database2 = { path = "../flowy-database2" }
 flowy-sqlite = { path = "../flowy-sqlite", optional = true }
-#flowy-document = { path = "../flowy-document" }
 flowy-document2 = { path = "../flowy-document2" }
-#flowy-revision = { path = "../flowy-revision" }
 flowy-error = { path = "../flowy-error" }
 flowy-task = { path = "../flowy-task" }
 flowy-server = { path = "../flowy-server" }
@@ -34,6 +31,7 @@ lib-ws = { path = "../../../shared-lib/lib-ws" }
 lib-infra = { path = "../../../shared-lib/lib-infra" }
 serde = "1.0"
 serde_json = "1.0"
+serde_repr = "0.1"
 
 [features]
 default = ["rev-sqlite"]

+ 5 - 3
frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs

@@ -13,8 +13,9 @@ use flowy_document2::document_data::DocumentDataWrapper;
 use flowy_document2::entities::DocumentDataPB;
 use flowy_document2::manager::DocumentManager;
 use flowy_error::FlowyError;
+use flowy_folder2::deps::{FolderCloudService, FolderUser};
 use flowy_folder2::entities::ViewLayoutPB;
-use flowy_folder2::manager::{Folder2Manager, FolderUser};
+use flowy_folder2::manager::Folder2Manager;
 use flowy_folder2::view_ext::{ViewDataProcessor, ViewDataProcessorMap};
 use flowy_folder2::ViewLayout;
 use flowy_user::services::UserSession;
@@ -27,13 +28,14 @@ impl Folder2DepsResolver {
     document_manager: &Arc<DocumentManager>,
     database_manager: &Arc<DatabaseManager2>,
     collab_builder: Arc<AppFlowyCollabBuilder>,
+    folder_cloud: Arc<dyn FolderCloudService>,
   ) -> Arc<Folder2Manager> {
     let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl(user_session.clone()));
 
-    let view_data_processor =
+    let view_processors =
       make_view_data_processor(document_manager.clone(), database_manager.clone());
     Arc::new(
-      Folder2Manager::new(user.clone(), collab_builder, view_data_processor)
+      Folder2Manager::new(user.clone(), collab_builder, view_processors, folder_cloud)
         .await
         .unwrap(),
     )

+ 110 - 17
frontend/rust-lib/flowy-core/src/integrate/server.rs

@@ -1,64 +1,134 @@
+use lib_infra::future::FutureResult;
 use std::collections::HashMap;
 use std::sync::Arc;
 
 use parking_lot::RwLock;
 
-use flowy_error::{ErrorCode, FlowyError};
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
+use flowy_folder2::deps::{FolderCloudService, Workspace};
 use flowy_server::local_server::LocalServer;
 use flowy_server::self_host::configuration::self_host_server_configuration;
 use flowy_server::self_host::SelfHostServer;
 use flowy_server::supabase::{SupabaseConfiguration, SupabaseServer};
 use flowy_server::AppFlowyServer;
+use flowy_sqlite::kv::KV;
 use flowy_user::event_map::{UserAuthService, UserCloudServiceProvider};
 use flowy_user::services::AuthType;
 
+use serde_repr::*;
+
+const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
+
+#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
+#[repr(u8)]
+pub enum ServerProviderType {
+  /// Local server provider.
+  /// Offline mode, no user authentication and the data is stored locally.
+  Local = 0,
+  /// Self-hosted server provider.
+  /// The [AppFlowy-Server](https://github.com/AppFlowy-IO/AppFlowy-Server) is still a work in
+  /// progress.
+  SelfHosted = 1,
+  /// Supabase server provider.
+  /// It uses supabase's postgresql database to store data and user authentication.
+  Supabase = 2,
+}
+
 /// The [AppFlowyServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
 /// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't
 /// exist.
 /// Each server implements the [AppFlowyServer] trait, which provides the [UserAuthService], etc.
-#[derive(Default)]
 pub struct AppFlowyServerProvider {
-  providers: RwLock<HashMap<AuthType, Arc<dyn AppFlowyServer>>>,
+  provider_type: RwLock<ServerProviderType>,
+  providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
 }
 
 impl AppFlowyServerProvider {
   pub fn new() -> Self {
     Self::default()
   }
+
+  pub fn provider_type(&self) -> ServerProviderType {
+    self.provider_type.read().clone()
+  }
+
+  /// Returns a [AppFlowyServer] trait implementation base on the provider_type.
+  fn get_provider(
+    &self,
+    provider_type: &ServerProviderType,
+  ) -> FlowyResult<Arc<dyn AppFlowyServer>> {
+    if let Some(provider) = self.providers.read().get(provider_type) {
+      return Ok(provider.clone());
+    }
+
+    let server = server_from_auth_type(provider_type)?;
+    self
+      .providers
+      .write()
+      .insert(provider_type.clone(), server.clone());
+    Ok(server)
+  }
+}
+
+impl Default for AppFlowyServerProvider {
+  fn default() -> Self {
+    Self {
+      provider_type: RwLock::new(current_server_provider()),
+      providers: RwLock::new(HashMap::new()),
+    }
+  }
 }
 
 impl UserCloudServiceProvider for AppFlowyServerProvider {
-  /// Returns the [UserAuthService] base on the current [AuthType].
+  /// When user login, the provider type is set by the [AuthType].
+  /// Each [AuthType] has a corresponding [ServerProviderType]. The [ServerProviderType] is used
+  /// to create a new [AppFlowyServer] if it doesn't exist. Once the [ServerProviderType] is set,
+  /// it will be used when user open the app again.
+  fn set_auth_type(&self, auth_type: AuthType) {
+    let provider_type: ServerProviderType = auth_type.into();
+    match KV::set_object(SERVER_PROVIDER_TYPE_KEY, provider_type.clone()) {
+      Ok(_) => tracing::trace!("Update server provider type to: {:?}", provider_type),
+      Err(e) => {
+        tracing::error!("🔴Failed to update server provider type: {:?}", e);
+      },
+    }
+  }
+
+  /// Returns the [UserAuthService] base on the current [ServerProviderType].
   /// Creates a new [AppFlowyServer] if it doesn't exist.
   fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
-    if let Some(provider) = self.providers.read().get(auth_type) {
-      return Ok(provider.user_service());
-    }
+    let provider_type: ServerProviderType = auth_type.into();
+    Ok(self.get_provider(&provider_type)?.user_service())
+  }
+}
 
-    let server = server_from_auth_type(auth_type)?;
-    let user_service = server.user_service();
-    self.providers.write().insert(auth_type.clone(), server);
-    Ok(user_service)
+impl FolderCloudService for AppFlowyServerProvider {
+  fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
+    let server = self.get_provider(&self.provider_type.read());
+    let name = name.to_string();
+    FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
   }
 }
 
-fn server_from_auth_type(auth_type: &AuthType) -> Result<Arc<dyn AppFlowyServer>, FlowyError> {
-  match auth_type {
-    AuthType::Local => {
+fn server_from_auth_type(
+  provider: &ServerProviderType,
+) -> Result<Arc<dyn AppFlowyServer>, FlowyError> {
+  match provider {
+    ServerProviderType::Local => {
       let server = Arc::new(LocalServer::new());
       Ok(server)
     },
-    AuthType::SelfHosted => {
+    ServerProviderType::SelfHosted => {
       let config = self_host_server_configuration().map_err(|e| {
         FlowyError::new(
           ErrorCode::InvalidAuthConfig,
-          format!("Missing self host config: {:?}. Error: {:?}", auth_type, e),
+          format!("Missing self host config: {:?}. Error: {:?}", provider, e),
         )
       })?;
       let server = Arc::new(SelfHostServer::new(config));
       Ok(server)
     },
-    AuthType::Supabase => {
+    ServerProviderType::Supabase => {
       // init the SupabaseServerConfiguration from the environment variables.
       let config = SupabaseConfiguration::from_env()?;
       let server = Arc::new(SupabaseServer::new(config));
@@ -66,3 +136,26 @@ fn server_from_auth_type(auth_type: &AuthType) -> Result<Arc<dyn AppFlowyServer>
     },
   }
 }
+
+impl From<AuthType> for ServerProviderType {
+  fn from(auth_provider: AuthType) -> Self {
+    match auth_provider {
+      AuthType::Local => ServerProviderType::Local,
+      AuthType::SelfHosted => ServerProviderType::SelfHosted,
+      AuthType::Supabase => ServerProviderType::Supabase,
+    }
+  }
+}
+
+impl From<&AuthType> for ServerProviderType {
+  fn from(auth_provider: &AuthType) -> Self {
+    Self::from(auth_provider.clone())
+  }
+}
+
+fn current_server_provider() -> ServerProviderType {
+  match KV::get_object::<ServerProviderType>(SERVER_PROVIDER_TYPE_KEY) {
+    None => ServerProviderType::Local,
+    Some(provider_type) => provider_type,
+  }
+}

+ 22 - 33
frontend/rust-lib/flowy-core/src/lib.rs

@@ -1,6 +1,5 @@
 #![allow(unused_doc_comments)]
 
-use std::str::FromStr;
 use std::time::Duration;
 use std::{
   fmt,
@@ -10,8 +9,8 @@ use std::{
   },
 };
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::config::{AWSDynamoDBConfig, AppFlowyCollabConfig};
+use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, CloudStorageType};
+
 use tokio::sync::RwLock;
 
 use flowy_database2::DatabaseManager2;
@@ -28,9 +27,10 @@ use lib_dispatch::runtime::tokio_default_runtime;
 use lib_infra::future::{to_fut, Fut};
 use module::make_plugins;
 pub use module::*;
+use tracing::debug;
 
 use crate::deps_resolve::*;
-use crate::integrate::server::AppFlowyServerProvider;
+use crate::integrate::server::{AppFlowyServerProvider, ServerProviderType};
 
 mod deps_resolve;
 mod integrate;
@@ -92,10 +92,8 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
   filters.push(format!("flowy_document2={}", level));
   filters.push(format!("flowy_database2={}", level));
   filters.push(format!("flowy_notification={}", "info"));
-  filters.push(format!("lib_ot={}", level));
   filters.push(format!("lib_infra={}", level));
   filters.push(format!("flowy_task={}", level));
-  // filters.push(format!("lib_dispatch={}", level));
 
   filters.push(format!("dart_ffi={}", "info"));
   filters.push(format!("flowy_sqlite={}", "info"));
@@ -136,22 +134,19 @@ impl AppFlowyCore {
     // Init the key value database
     init_kv(&config.storage_path);
 
-    // The collab config is used to build the [Collab] instance that used in document,
-    // database, folder, etc.
-    let collab_config = get_collab_config();
-    inject_aws_env(collab_config.aws_config());
-
-    /// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
-    /// on demand based on the [AppFlowyCollabConfig].
-    let collab_builder = Arc::new(AppFlowyCollabBuilder::new(collab_config));
-
-    tracing::debug!("🔥 {:?}", config);
+    debug!("🔥 {:?}", &config);
     let runtime = tokio_default_runtime().unwrap();
     let task_scheduler = TaskDispatcher::new(Duration::from_secs(2));
     let task_dispatcher = Arc::new(RwLock::new(task_scheduler));
     runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
 
     let server_provider = Arc::new(AppFlowyServerProvider::new());
+    /// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
+    /// on demand based on the [CollabPluginConfig].
+    let cloud_storage_type =
+      collab_storage_type_from_server_provider_type(&server_provider.provider_type());
+    let collab_builder = Arc::new(AppFlowyCollabBuilder::new(cloud_storage_type));
+
     let (user_session, folder_manager, server_provider, database_manager, document_manager2) =
       runtime.block_on(async {
         let user_session = mk_user_session(&config, server_provider.clone());
@@ -173,6 +168,7 @@ impl AppFlowyCore {
           &document_manager2,
           &database_manager2,
           collab_builder.clone(),
+          server_provider.clone(),
         )
         .await;
 
@@ -233,23 +229,6 @@ fn init_kv(root: &str) {
   }
 }
 
-fn get_collab_config() -> AppFlowyCollabConfig {
-  match KV::get_str("collab_config") {
-    None => AppFlowyCollabConfig::default(),
-    Some(s) => AppFlowyCollabConfig::from_str(&s).unwrap_or_default(),
-  }
-}
-
-fn inject_aws_env(aws_config: Option<&AWSDynamoDBConfig>) {
-  if let Some(aws_config) = aws_config {
-    std::env::set_var("AWS_ACCESS_KEY_ID", aws_config.access_key_id.clone());
-    std::env::set_var(
-      "AWS_SECRET_ACCESS_KEY",
-      aws_config.secret_access_key.clone(),
-    );
-  }
-}
-
 fn init_log(config: &AppFlowyCoreConfig) {
   if !INIT_LOG.load(Ordering::SeqCst) {
     INIT_LOG.store(true, Ordering::SeqCst);
@@ -334,3 +313,13 @@ impl UserStatusCallback for UserStatusCallbackImpl {
     to_fut(async move { listener.did_expired(&token, user_id).await })
   }
 }
+
+fn collab_storage_type_from_server_provider_type(
+  server_provider_type: &ServerProviderType,
+) -> CloudStorageType {
+  match server_provider_type {
+    ServerProviderType::Local => CloudStorageType::Local,
+    ServerProviderType::SelfHosted => CloudStorageType::Local,
+    ServerProviderType::Supabase => CloudStorageType::Supabase,
+  }
+}

+ 1 - 2
frontend/rust-lib/flowy-database2/src/event_handler.rs

@@ -334,7 +334,7 @@ pub(crate) async fn create_row_handler(
   }
 }
 
-#[tracing::instrument(level = "trace", skip_all, err)]
+// #[tracing::instrument(level = "trace", skip_all, err)]
 pub(crate) async fn get_cell_handler(
   data: AFPluginData<CellIdPB>,
   manager: AFPluginState<Arc<DatabaseManager2>>,
@@ -560,7 +560,6 @@ pub(crate) async fn set_layout_setting_handler(
   Ok(())
 }
 
-#[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn get_layout_setting_handler(
   data: AFPluginData<DatabaseLayoutIdPB>,
   manager: AFPluginState<Arc<DatabaseManager2>>,

+ 3 - 3
frontend/rust-lib/flowy-database2/src/manager.rs

@@ -3,7 +3,7 @@ use std::ops::Deref;
 use std::sync::Arc;
 
 use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::{RocksCollabDB, RocksDBConfig};
+use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
 use collab::core::collab::MutexCollab;
 use collab_database::database::DatabaseData;
 use collab_database::user::{UserDatabase as InnerUserDatabase, UserDatabaseCollabBuilder};
@@ -51,7 +51,7 @@ impl DatabaseManager2 {
     *self.user_database.lock() = Some(InnerUserDatabase::new(
       user_id,
       db,
-      RocksDBConfig::default(),
+      CollabPersistenceConfig::default(),
       UserDatabaseCollabBuilderImpl(self.collab_builder.clone()),
     ));
     // do nothing
@@ -229,7 +229,7 @@ impl UserDatabaseCollabBuilder for UserDatabaseCollabBuilderImpl {
     uid: i64,
     object_id: &str,
     db: Arc<RocksCollabDB>,
-    config: &RocksDBConfig,
+    config: &CollabPersistenceConfig,
   ) -> Arc<MutexCollab> {
     self.0.build_with_config(uid, object_id, db, config)
   }

+ 22 - 12
frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs

@@ -3,8 +3,9 @@ use std::sync::Arc;
 use collab_database::fields::Field;
 use collab_database::rows::RowId;
 
-use flowy_error::FlowyResult;
+use flowy_error::{FlowyError, FlowyResult};
 use lib_infra::future::{to_fut, Fut};
+use tracing::trace;
 
 use crate::entities::FieldType;
 use crate::services::database_view::DatabaseViewData;
@@ -42,9 +43,10 @@ pub async fn new_group_controller(
   let fields = delegate.get_fields(&view_id, None).await;
   let rows = delegate.get_rows(&view_id).await;
   let layout = delegate.get_layout_for_view(&view_id);
+  trace!(?fields, ?rows, ?layout, "new_group_controller");
 
   // Read the grouping field or find a new grouping field
-  let grouping_field = setting_reader
+  let mut grouping_field = setting_reader
     .get_group_setting(&view_id)
     .await
     .and_then(|setting| {
@@ -52,17 +54,25 @@ pub async fn new_group_controller(
         .iter()
         .find(|field| field.id == setting.field_id)
         .cloned()
-    })
-    .unwrap_or_else(|| find_new_grouping_field(&fields, &layout).unwrap());
+    });
 
-  make_group_controller(
-    view_id,
-    grouping_field,
-    rows,
-    setting_reader,
-    setting_writer,
-  )
-  .await
+  if grouping_field.is_none() {
+    grouping_field = find_new_grouping_field(&fields, &layout);
+  }
+
+  match grouping_field {
+    None => Err(FlowyError::internal().context("No grouping field found".to_owned())),
+    Some(_) => {
+      make_group_controller(
+        view_id,
+        grouping_field.unwrap(),
+        rows,
+        setting_reader,
+        setting_writer,
+      )
+      .await
+    },
+  }
 }
 
 pub(crate) struct GroupSettingReaderImpl(pub Arc<dyn DatabaseViewData>);

+ 1 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/format.rs

@@ -7,15 +7,9 @@ use strum::IntoEnumIterator;
 use strum_macros::EnumIter;
 
 lazy_static! {
-  pub static ref CURRENCY_SYMBOL: Vec<String> = sorted_symbol();
-}
-
-fn sorted_symbol() -> Vec<String> {
-  let mut symbols = NumberFormat::iter()
+  pub static ref CURRENCY_SYMBOL: Vec<String> = NumberFormat::iter()
     .map(|format| format.symbol())
     .collect::<Vec<String>>();
-  symbols.sort_by(|a, b| b.len().cmp(&a.len()));
-  symbols
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize)]

+ 1 - 3
frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -137,9 +137,7 @@ impl NumberTypeOption {
           };
 
           match Decimal::from_str(&num_str) {
-            Ok(decimal, ..) => {
-              return Ok(NumberCellFormat::from_decimal(decimal));
-            },
+            Ok(decimal, ..) => Ok(NumberCellFormat::from_decimal(decimal)),
             Err(_) => Ok(NumberCellFormat::new()),
           }
         }

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option_entities.rs

@@ -27,7 +27,7 @@ impl NumberCellFormat {
       return Ok(Self::default());
     }
     // If the first char is not '-', then it is a sign.
-    let sign_positive = match num_str.find("-") {
+    let sign_positive = match num_str.find('-') {
       None => true,
       Some(offset) => offset != 0,
     };

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs

@@ -162,7 +162,7 @@ where
   ) {
     if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
       let field_type = FieldType::from(field.field_type);
-      let key = CellDataCacheKey::new(field, field_type.clone(), cell);
+      let key = CellDataCacheKey::new(field, field_type, cell);
       // tracing::trace!(
       //   "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
       //   field_type,

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/sort/controller.rs

@@ -85,7 +85,7 @@ impl SortController {
     self.gen_task(task_type, QualityOfService::Background).await;
   }
 
-  #[tracing::instrument(name = "process_sort_task", level = "debug", skip_all, err)]
+  // #[tracing::instrument(name = "process_sort_task", level = "trace", skip_all, err)]
   pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> {
     let event_type = SortEvent::from_str(predicate).unwrap();
     let mut rows = self.delegate.get_rows(&self.view_id).await;

+ 4 - 4
frontend/rust-lib/flowy-database2/tests/database/database_editor.rs

@@ -13,13 +13,13 @@ use flowy_database2::services::field::{
   SelectOptionCellChangeset, SingleSelectTypeOption,
 };
 use flowy_error::FlowyResult;
-use flowy_test::helper::ViewTest;
-use flowy_test::FlowySDKTest;
+use flowy_test::folder_event::ViewTest;
+use flowy_test::FlowyCoreTest;
 
 use crate::database::mock_data::{make_test_board, make_test_calendar, make_test_grid};
 
 pub struct DatabaseEditorTest {
-  pub sdk: FlowySDKTest,
+  pub sdk: FlowyCoreTest,
   pub app_id: String,
   pub view_id: String,
   pub editor: Arc<DatabaseEditor>,
@@ -43,7 +43,7 @@ impl DatabaseEditorTest {
   }
 
   pub async fn new(layout: DatabaseLayoutPB) -> Self {
-    let sdk = FlowySDKTest::default();
+    let sdk = FlowyCoreTest::new();
     let _ = sdk.init_user().await;
     let test = match layout {
       DatabaseLayoutPB::Grid => {

+ 0 - 425
frontend/rust-lib/flowy-database2/tests/database/script.rs

@@ -1,425 +0,0 @@
-use bytes::Bytes;
-use database_model::entities::{
-  BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType,
-  GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset,
-  RowOrder, TypeOptionDataFormat,
-};
-use flowy_client_sync::client_grid::GridBuilder;
-use flowy_database::services::field::*;
-use flowy_database::services::grid_meta_editor::{GridMetaEditor, GridPadBuilder};
-use flowy_database::services::row::CreateRowMetaPayload;
-use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
-use flowy_test::helper::ViewTest;
-use flowy_test::FlowySDKTest;
-use std::collections::HashMap;
-use std::sync::Arc;
-use std::time::Duration;
-use strum::EnumCount;
-use tokio::time::sleep;
-
-pub enum EditorScript {
-  CreateField {
-    params: InsertFieldParams,
-  },
-  UpdateField {
-    changeset: FieldChangesetParams,
-  },
-  DeleteField {
-    field_meta: FieldMeta,
-  },
-  AssertFieldCount(usize),
-  AssertFieldEqual {
-    field_index: usize,
-    field_meta: FieldMeta,
-  },
-  CreateBlock {
-    block: GridBlockMetaSnapshot,
-  },
-  UpdateBlock {
-    changeset: GridBlockInfoChangeset,
-  },
-  AssertBlockCount(usize),
-  AssertBlock {
-    block_index: usize,
-    row_count: i32,
-    start_row_index: i32,
-  },
-  AssertBlockEqual {
-    block_index: usize,
-    block: GridBlockMetaSnapshot,
-  },
-  CreateEmptyRow,
-  CreateRow {
-    context: CreateRowMetaPayload,
-  },
-  UpdateRow {
-    changeset: RowMetaChangeset,
-  },
-  AssertRow {
-    changeset: RowMetaChangeset,
-  },
-  DeleteRow {
-    row_ids: Vec<String>,
-  },
-  UpdateCell {
-    changeset: CellChangeset,
-    is_err: bool,
-  },
-  AssertRowCount(usize),
-  // AssertRowEqual{ row_index: usize, row: RowMeta},
-  AssertGridMetaPad,
-}
-
-pub struct GridEditorTest {
-  pub sdk: FlowySDKTest,
-  pub grid_id: String,
-  pub editor: Arc<GridMetaEditor>,
-  pub field_metas: Vec<FieldMeta>,
-  pub grid_blocks: Vec<GridBlockMetaSnapshot>,
-  pub row_metas: Vec<Arc<RowMeta>>,
-  pub field_count: usize,
-
-  pub row_order_by_row_id: HashMap<String, RowOrder>,
-}
-
-impl GridEditorTest {
-  pub async fn new() -> Self {
-    let sdk = FlowySDKTest::default();
-    let _ = sdk.init_user().await;
-    let build_context = make_template_1_grid();
-    let view_data: Bytes = build_context.into();
-    let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
-    let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
-    let field_metas = editor.get_field_metas::<FieldOrder>(None).await.unwrap();
-    let grid_blocks = editor.get_block_metas().await.unwrap();
-    let row_metas = get_row_metas(&editor).await;
-
-    let grid_id = test.view.id;
-    Self {
-      sdk,
-      grid_id,
-      editor,
-      field_metas,
-      grid_blocks,
-      row_metas,
-      field_count: FieldType::COUNT,
-      row_order_by_row_id: HashMap::default(),
-    }
-  }
-
-  pub async fn run_scripts(&mut self, scripts: Vec<EditorScript>) {
-    for script in scripts {
-      self.run_script(script).await;
-    }
-  }
-
-  pub async fn run_script(&mut self, script: EditorScript) {
-    let grid_manager = self.sdk.grid_manager.clone();
-    let pool = self.sdk.user_session.db_pool().unwrap();
-    let rev_manager = self.editor.rev_manager();
-    let _cache = rev_manager.revision_cache().await;
-
-    match script {
-      EditorScript::CreateField { params } => {
-        if !self.editor.contain_field(&params.field.id).await {
-          self.field_count += 1;
-        }
-
-        self.editor.insert_field(params).await.unwrap();
-        self.field_metas = self
-          .editor
-          .get_field_metas::<FieldOrder>(None)
-          .await
-          .unwrap();
-        assert_eq!(self.field_count, self.field_metas.len());
-      },
-      EditorScript::UpdateField { changeset: change } => {
-        self.editor.update_field(change).await.unwrap();
-        self.field_metas = self
-          .editor
-          .get_field_metas::<FieldOrder>(None)
-          .await
-          .unwrap();
-      },
-      EditorScript::DeleteField { field_meta } => {
-        if self.editor.contain_field(&field_meta.id).await {
-          self.field_count -= 1;
-        }
-
-        self.editor.delete_field(&field_meta.id).await.unwrap();
-        self.field_metas = self
-          .editor
-          .get_field_metas::<FieldOrder>(None)
-          .await
-          .unwrap();
-        assert_eq!(self.field_count, self.field_metas.len());
-      },
-      EditorScript::AssertFieldCount(count) => {
-        assert_eq!(
-          self
-            .editor
-            .get_field_metas::<FieldOrder>(None)
-            .await
-            .unwrap()
-            .len(),
-          count
-        );
-      },
-      EditorScript::AssertFieldEqual {
-        field_index,
-        field_meta,
-      } => {
-        let field_metas = self
-          .editor
-          .get_field_metas::<FieldOrder>(None)
-          .await
-          .unwrap();
-        assert_eq!(field_metas[field_index].clone(), field_meta);
-      },
-      EditorScript::CreateBlock { block } => {
-        self.editor.create_block(block).await.unwrap();
-        self.grid_blocks = self.editor.get_block_metas().await.unwrap();
-      },
-      EditorScript::UpdateBlock { changeset: change } => {
-        self.editor.update_block(change).await.unwrap();
-      },
-      EditorScript::AssertBlockCount(count) => {
-        assert_eq!(self.editor.get_block_metas().await.unwrap().len(), count);
-      },
-      EditorScript::AssertBlock {
-        block_index,
-        row_count,
-        start_row_index,
-      } => {
-        assert_eq!(self.grid_blocks[block_index].row_count, row_count);
-        assert_eq!(
-          self.grid_blocks[block_index].start_row_index,
-          start_row_index
-        );
-      },
-      EditorScript::AssertBlockEqual { block_index, block } => {
-        let blocks = self.editor.get_block_metas().await.unwrap();
-        let compared_block = blocks[block_index].clone();
-        assert_eq!(compared_block, block);
-      },
-      EditorScript::CreateEmptyRow => {
-        let row_order = self.editor.create_row(None).await.unwrap();
-        self
-          .row_order_by_row_id
-          .insert(row_order.row_id.clone(), row_order);
-        self.row_metas = self.get_row_metas().await;
-        self.grid_blocks = self.editor.get_block_metas().await.unwrap();
-      },
-      EditorScript::CreateRow { context } => {
-        let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
-        for row_order in row_orders {
-          self
-            .row_order_by_row_id
-            .insert(row_order.row_id.clone(), row_order);
-        }
-        self.row_metas = self.get_row_metas().await;
-        self.grid_blocks = self.editor.get_block_metas().await.unwrap();
-      },
-      EditorScript::UpdateRow { changeset: change } => {
-        self.editor.update_row(change).await.unwrap()
-      },
-      EditorScript::DeleteRow { row_ids } => {
-        let row_orders = row_ids
-          .into_iter()
-          .map(|row_id| self.row_order_by_row_id.get(&row_id).unwrap().clone())
-          .collect::<Vec<RowOrder>>();
-
-        self.editor.delete_rows(row_orders).await.unwrap();
-        self.row_metas = self.get_row_metas().await;
-        self.grid_blocks = self.editor.get_block_metas().await.unwrap();
-      },
-      EditorScript::AssertRow { changeset } => {
-        let row = self
-          .row_metas
-          .iter()
-          .find(|row| row.id == changeset.row_id)
-          .unwrap();
-
-        if let Some(visibility) = changeset.visibility {
-          assert_eq!(row.visibility, visibility);
-        }
-
-        if let Some(height) = changeset.height {
-          assert_eq!(row.height, height);
-        }
-      },
-      EditorScript::UpdateCell { changeset, is_err } => {
-        let result = self.editor.update_cell(changeset).await;
-        if is_err {
-          assert!(result.is_err())
-        } else {
-          let _ = result.unwrap();
-          self.row_metas = self.get_row_metas().await;
-        }
-      },
-      EditorScript::AssertRowCount(count) => {
-        assert_eq!(self.row_metas.len(), count);
-      },
-      EditorScript::AssertGridMetaPad => {
-        sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;
-        let mut grid_rev_manager = grid_manager
-          .make_grid_rev_manager(&self.grid_id, pool.clone())
-          .unwrap();
-        let grid_pad = grid_rev_manager.load::<GridPadBuilder>(None).await.unwrap();
-        println!("{}", grid_pad.delta_str());
-      },
-    }
-  }
-
-  async fn get_row_metas(&self) -> Vec<Arc<RowMeta>> {
-    get_row_metas(&self.editor).await
-  }
-}
-
-async fn get_row_metas(editor: &Arc<GridMetaEditor>) -> Vec<Arc<RowMeta>> {
-  editor
-    .grid_block_snapshots(None)
-    .await
-    .unwrap()
-    .pop()
-    .unwrap()
-    .row_metas
-}
-
-pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
-  let field_meta = FieldBuilder::new(RichTextTypeOptionBuilder::default())
-    .name("Name")
-    .visibility(true)
-    .build();
-
-  let cloned_field_meta = field_meta.clone();
-
-  let type_option_data = field_meta
-    .get_type_option_entry::<RichTextTypeOptionPB>(&field_meta.field_type)
-    .unwrap()
-    .protobuf_bytes()
-    .to_vec();
-
-  let field = Field {
-    id: field_meta.id,
-    name: field_meta.name,
-    desc: field_meta.desc,
-    field_type: field_meta.field_type,
-    frozen: field_meta.frozen,
-    visibility: field_meta.visibility,
-    width: field_meta.width,
-    is_primary: false,
-  };
-
-  let params = InsertFieldParams {
-    grid_id: grid_id.to_owned(),
-    field,
-    type_option_data,
-    start_field_id: None,
-  };
-  (params, cloned_field_meta)
-}
-
-pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
-  let single_select = SingleSelectTypeOptionBuilder::default()
-    .option(SelectOption::new("Done"))
-    .option(SelectOption::new("Progress"));
-
-  let field_meta = FieldBuilder::new(single_select)
-    .name("Name")
-    .visibility(true)
-    .build();
-  let cloned_field_meta = field_meta.clone();
-  let type_option_data = field_meta
-    .get_type_option_entry::<SingleSelectTypeOption>(&field_meta.field_type)
-    .unwrap()
-    .protobuf_bytes()
-    .to_vec();
-
-  let field = Field {
-    id: field_meta.id,
-    name: field_meta.name,
-    desc: field_meta.desc,
-    field_type: field_meta.field_type,
-    frozen: field_meta.frozen,
-    visibility: field_meta.visibility,
-    width: field_meta.width,
-    is_primary: false,
-  };
-
-  let params = InsertFieldParams {
-    grid_id: grid_id.to_owned(),
-    field,
-    type_option_data,
-    start_field_id: None,
-  };
-  (params, cloned_field_meta)
-}
-
-fn make_template_1_grid() -> BuildGridContext {
-  let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
-    .name("Name")
-    .visibility(true)
-    .build();
-
-  // Single Select
-  let single_select = SingleSelectTypeOptionBuilder::default()
-    .option(SelectOption::new("Live"))
-    .option(SelectOption::new("Completed"))
-    .option(SelectOption::new("Planned"))
-    .option(SelectOption::new("Paused"));
-  let single_select_field = FieldBuilder::new(single_select)
-    .name("Status")
-    .visibility(true)
-    .build();
-
-  // MultiSelect
-  let multi_select = MultiSelectTypeOptionBuilder::default()
-    .option(SelectOption::new("Google"))
-    .option(SelectOption::new("Facebook"))
-    .option(SelectOption::new("Twitter"));
-  let multi_select_field = FieldBuilder::new(multi_select)
-    .name("Platform")
-    .visibility(true)
-    .build();
-
-  // Number
-  let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
-  let number_field = FieldBuilder::new(number)
-    .name("Price")
-    .visibility(true)
-    .build();
-
-  // Date
-  let date = DateTypeOptionBuilder::default()
-    .date_format(DateFormat::US)
-    .time_format(TimeFormat::TwentyFourHour);
-  let date_field = FieldBuilder::new(date)
-    .name("Time")
-    .visibility(true)
-    .build();
-
-  // Checkbox
-  let checkbox = CheckboxTypeOptionBuilder::default();
-  let checkbox_field = FieldBuilder::new(checkbox)
-    .name("is done")
-    .visibility(true)
-    .build();
-
-  // URL
-  let url = URLTypeOptionBuilder::default();
-  let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
-
-  GridBuilder::default()
-    .add_field(text_field)
-    .add_field(single_select_field)
-    .add_field(multi_select_field)
-    .add_field(number_field)
-    .add_field(date_field)
-    .add_field(checkbox_field)
-    .add_field(url_field)
-    .add_empty_row()
-    .add_empty_row()
-    .add_empty_row()
-    .build()
-}

+ 3 - 3
frontend/rust-lib/flowy-document2/tests/document/util.rs

@@ -1,5 +1,5 @@
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::config::AppFlowyCollabConfig;
+use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, CloudStorageType};
+
 use std::sync::Arc;
 
 use appflowy_integrate::RocksCollabDB;
@@ -50,6 +50,6 @@ pub fn db() -> Arc<RocksCollabDB> {
 }
 
 pub fn default_collab_builder() -> Arc<AppFlowyCollabBuilder> {
-  let builder = AppFlowyCollabBuilder::new(AppFlowyCollabConfig::default());
+  let builder = AppFlowyCollabBuilder::new(CloudStorageType::Local);
   Arc::new(builder)
 }

+ 17 - 0
frontend/rust-lib/flowy-folder2/src/deps.rs

@@ -0,0 +1,17 @@
+use appflowy_integrate::RocksCollabDB;
+pub use collab_folder::core::Workspace;
+use flowy_error::FlowyError;
+use lib_infra::future::FutureResult;
+use std::sync::Arc;
+
+/// [FolderUser] represents the user for folder.
+pub trait FolderUser: Send + Sync {
+  fn user_id(&self) -> Result<i64, FlowyError>;
+  fn token(&self) -> Result<Option<String>, FlowyError>;
+  fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
+}
+
+/// [FolderCloudService] represents the cloud service for folder.
+pub trait FolderCloudService: Send + Sync + 'static {
+  fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, FlowyError>;
+}

+ 1 - 1
frontend/rust-lib/flowy-folder2/src/entities/workspace.rs

@@ -106,7 +106,7 @@ impl WorkspaceIdPB {
   }
 }
 
-#[derive(Default, ProtoBuf, Clone)]
+#[derive(Default, ProtoBuf, Debug, Clone)]
 pub struct WorkspaceSettingPB {
   #[pb(index = 1)]
   pub workspace: WorkspacePB,

+ 1 - 0
frontend/rust-lib/flowy-folder2/src/event_map.rs

@@ -3,6 +3,7 @@ use crate::manager::Folder2Manager;
 use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
 
 use lib_dispatch::prelude::*;
+
 use std::sync::Arc;
 use strum_macros::Display;
 

+ 2 - 0
frontend/rust-lib/flowy-folder2/src/lib.rs

@@ -7,7 +7,9 @@ pub mod protobuf;
 mod user_default;
 pub mod view_ext;
 
+pub mod deps;
 #[cfg(feature = "test_helper")]
 mod test_helper;
 
 pub use collab_folder::core::ViewLayout;
+pub use user_default::gen_workspace_id;

+ 11 - 16
frontend/rust-lib/flowy-folder2/src/manager.rs

@@ -3,7 +3,7 @@ use std::ops::Deref;
 use std::sync::Arc;
 
 use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::RocksCollabDB;
+
 use collab_folder::core::{
   Folder as InnerFolder, FolderContext, TrashChange, TrashChangeReceiver, TrashInfo, TrashRecord,
   View, ViewChange, ViewChangeReceiver, ViewLayout, Workspace,
@@ -11,6 +11,7 @@ use collab_folder::core::{
 use parking_lot::Mutex;
 use tracing::{event, Level};
 
+use crate::deps::{FolderCloudService, FolderUser};
 use flowy_error::{FlowyError, FlowyResult};
 use lib_infra::util::timestamp;
 
@@ -22,22 +23,17 @@ use crate::notification::{
   send_notification, send_workspace_notification, send_workspace_setting_notification,
   FolderNotification,
 };
-use crate::user_default::{gen_workspace_id, DefaultFolderBuilder};
+use crate::user_default::DefaultFolderBuilder;
 use crate::view_ext::{
   gen_view_id, view_from_create_view_params, ViewDataProcessor, ViewDataProcessorMap,
 };
 
-pub trait FolderUser: Send + Sync {
-  fn user_id(&self) -> Result<i64, FlowyError>;
-  fn token(&self) -> Result<Option<String>, FlowyError>;
-  fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
-}
-
 pub struct Folder2Manager {
   folder: Folder,
   collab_builder: Arc<AppFlowyCollabBuilder>,
   user: Arc<dyn FolderUser>,
   view_processors: ViewDataProcessorMap,
+  cloud_service: Arc<dyn FolderCloudService>,
 }
 
 unsafe impl Send for Folder2Manager {}
@@ -48,6 +44,7 @@ impl Folder2Manager {
     user: Arc<dyn FolderUser>,
     collab_builder: Arc<AppFlowyCollabBuilder>,
     view_processors: ViewDataProcessorMap,
+    cloud_service: Arc<dyn FolderCloudService>,
   ) -> FlowyResult<Self> {
     let folder = Folder::default();
     let manager = Self {
@@ -55,6 +52,7 @@ impl Folder2Manager {
       folder,
       collab_builder,
       view_processors,
+      cloud_service,
     };
 
     Ok(manager)
@@ -90,7 +88,7 @@ impl Folder2Manager {
   }
 
   /// Called immediately after the application launched fi the user already sign in/sign up.
-  #[tracing::instrument(level = "trace", skip(self), err)]
+  #[tracing::instrument(level = "debug", skip(self), err)]
   pub async fn initialize(&self, uid: i64, workspace_id: &str) -> FlowyResult<()> {
     if let Ok(collab_db) = self.user.collab_db() {
       let collab = self.collab_builder.build(uid, workspace_id, collab_db);
@@ -139,13 +137,10 @@ impl Folder2Manager {
   pub async fn clear(&self, _user_id: i64) {}
 
   pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> FlowyResult<Workspace> {
-    let workspace = Workspace {
-      id: gen_workspace_id(),
-      name: params.name,
-      belongings: Default::default(),
-      created_at: timestamp(),
-    };
-
+    let workspace = self
+      .cloud_service
+      .create_workspace(self.user.user_id()?, &params.name)
+      .await?;
     self.with_folder((), |folder| {
       folder.workspaces.create_workspace(workspace.clone());
       folder.set_current_workspace(&workspace.id);

+ 4 - 3
frontend/rust-lib/flowy-folder2/tests/workspace/folder_test.rs

@@ -1,7 +1,7 @@
 use crate::script::{invalid_workspace_name_test_case, FolderScript::*, FolderTest};
 use collab_folder::core::ViewLayout;
 use flowy_folder2::entities::CreateWorkspacePayloadPB;
-use flowy_test::{event_builder::*, FlowySDKTest};
+use flowy_test::{event_builder::*, FlowyCoreTest};
 
 #[tokio::test]
 async fn workspace_read_all() {
@@ -63,18 +63,19 @@ async fn workspace_create_with_apps() {
 #[tokio::test]
 async fn workspace_create_with_invalid_name() {
   for (name, code) in invalid_workspace_name_test_case() {
-    let sdk = FlowySDKTest::default();
+    let sdk = FlowyCoreTest::new();
     let request = CreateWorkspacePayloadPB {
       name,
       desc: "".to_owned(),
     };
     assert_eq!(
-      Folder2EventBuilder::new(sdk)
+      EventBuilder::new(sdk)
         .event(flowy_folder2::event_map::FolderEvent::CreateWorkspace)
         .payload(request)
         .async_send()
         .await
         .error()
+        .unwrap()
         .code,
       code.value()
     )

+ 26 - 26
frontend/rust-lib/flowy-folder2/tests/workspace/script.rs

@@ -2,8 +2,8 @@ use collab_folder::core::ViewLayout;
 use flowy_error::ErrorCode;
 use flowy_folder2::entities::*;
 use flowy_folder2::event_map::FolderEvent::*;
-use flowy_test::event_builder::Folder2EventBuilder;
-use flowy_test::FlowySDKTest;
+use flowy_test::event_builder::EventBuilder;
+use flowy_test::FlowyCoreTest;
 
 pub enum FolderScript {
   // Workspace
@@ -51,7 +51,7 @@ pub enum FolderScript {
 }
 
 pub struct FolderTest {
-  pub sdk: FlowySDKTest,
+  pub sdk: FlowyCoreTest,
   pub all_workspace: Vec<WorkspacePB>,
   pub workspace: WorkspacePB,
   pub parent_view: ViewPB,
@@ -61,7 +61,7 @@ pub struct FolderTest {
 
 impl FolderTest {
   pub async fn new() -> Self {
-    let sdk = FlowySDKTest::default();
+    let sdk = FlowyCoreTest::new();
     let _ = sdk.init_user().await;
     let workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await;
     let parent_view = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await;
@@ -170,13 +170,13 @@ pub fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> {
   ]
 }
 
-pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB {
+pub async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
   let request = CreateWorkspacePayloadPB {
     name: name.to_owned(),
     desc: desc.to_owned(),
   };
 
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(CreateWorkspace)
     .payload(request)
     .async_send()
@@ -184,11 +184,11 @@ pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> Wor
     .parse::<WorkspacePB>()
 }
 
-pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option<String>) -> Vec<WorkspacePB> {
+pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -> Vec<WorkspacePB> {
   let request = WorkspaceIdPB {
     value: workspace_id,
   };
-  let repeated_workspace = Folder2EventBuilder::new(sdk.clone())
+  let repeated_workspace = EventBuilder::new(sdk.clone())
     .event(ReadWorkspaces)
     .payload(request.clone())
     .async_send()
@@ -210,7 +210,7 @@ pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option<String>) ->
   workspaces
 }
 
-pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
+pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
   let create_view_request = CreateViewPayloadPB {
     belong_to_id: workspace_id.to_owned(),
     name: name.to_string(),
@@ -221,7 +221,7 @@ pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc
     ext: Default::default(),
   };
 
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(CreateView)
     .payload(create_view_request)
     .async_send()
@@ -230,7 +230,7 @@ pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc
 }
 
 pub async fn create_view(
-  sdk: &FlowySDKTest,
+  sdk: &FlowyCoreTest,
   app_id: &str,
   name: &str,
   desc: &str,
@@ -245,7 +245,7 @@ pub async fn create_view(
     initial_data: vec![],
     ext: Default::default(),
   };
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(CreateView)
     .payload(request)
     .async_send()
@@ -253,9 +253,9 @@ pub async fn create_view(
     .parse::<ViewPB>()
 }
 
-pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> ViewPB {
+pub async fn read_view(sdk: &FlowyCoreTest, view_id: &str) -> ViewPB {
   let view_id: ViewIdPB = view_id.into();
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(ReadView)
     .payload(view_id)
     .async_send()
@@ -264,7 +264,7 @@ pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> ViewPB {
 }
 
 pub async fn update_view(
-  sdk: &FlowySDKTest,
+  sdk: &FlowyCoreTest,
   view_id: &str,
   name: Option<String>,
   desc: Option<String>,
@@ -275,54 +275,54 @@ pub async fn update_view(
     desc,
     thumbnail: None,
   };
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(UpdateView)
     .payload(request)
     .async_send()
     .await;
 }
 
-pub async fn delete_view(sdk: &FlowySDKTest, view_ids: Vec<String>) {
+pub async fn delete_view(sdk: &FlowyCoreTest, view_ids: Vec<String>) {
   let request = RepeatedViewIdPB { items: view_ids };
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(DeleteView)
     .payload(request)
     .async_send()
     .await;
 }
 
-pub async fn read_trash(sdk: &FlowySDKTest) -> RepeatedTrashPB {
-  Folder2EventBuilder::new(sdk.clone())
+pub async fn read_trash(sdk: &FlowyCoreTest) -> RepeatedTrashPB {
+  EventBuilder::new(sdk.clone())
     .event(ReadTrash)
     .async_send()
     .await
     .parse::<RepeatedTrashPB>()
 }
 
-pub async fn restore_app_from_trash(sdk: &FlowySDKTest, app_id: &str) {
+pub async fn restore_app_from_trash(sdk: &FlowyCoreTest, app_id: &str) {
   let id = TrashIdPB {
     id: app_id.to_owned(),
   };
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(PutbackTrash)
     .payload(id)
     .async_send()
     .await;
 }
 
-pub async fn restore_view_from_trash(sdk: &FlowySDKTest, view_id: &str) {
+pub async fn restore_view_from_trash(sdk: &FlowyCoreTest, view_id: &str) {
   let id = TrashIdPB {
     id: view_id.to_owned(),
   };
-  Folder2EventBuilder::new(sdk.clone())
+  EventBuilder::new(sdk.clone())
     .event(PutbackTrash)
     .payload(id)
     .async_send()
     .await;
 }
 
-pub async fn delete_all_trash(sdk: &FlowySDKTest) {
-  Folder2EventBuilder::new(sdk.clone())
+pub async fn delete_all_trash(sdk: &FlowyCoreTest) {
+  EventBuilder::new(sdk.clone())
     .event(DeleteAllTrash)
     .async_send()
     .await;

+ 2 - 1
frontend/rust-lib/flowy-server/Cargo.toml

@@ -24,11 +24,12 @@ postgrest = "1.0"
 tokio-retry = "0.3"
 anyhow = "1.0"
 uuid = { version = "1.3.3", features = ["v4"] }
+chrono = "0.4.24"
 
 lib-infra = { path = "../../../shared-lib/lib-infra" }
 flowy-user = { path = "../flowy-user" }
+flowy-folder2 = { path = "../flowy-folder2" }
 flowy-error = { path = "../flowy-error" }
-flowy-config = { path = "../flowy-config" }
 
 [dev-dependencies]
 uuid = { version = "1.3.3", features = ["v4"] }

+ 16 - 0
frontend/rust-lib/flowy-server/src/lib.rs

@@ -1,3 +1,4 @@
+use flowy_folder2::deps::FolderCloudService;
 use std::sync::Arc;
 
 use flowy_user::event_map::UserAuthService;
@@ -8,6 +9,21 @@ mod response;
 pub mod self_host;
 pub mod supabase;
 
+/// In order to run this the supabase test, you need to create a .env file in the root directory of this project
+/// and add the following environment variables:
+/// - SUPABASE_URL
+/// - SUPABASE_ANON_KEY
+/// - SUPABASE_KEY
+/// - SUPABASE_JWT_SECRET
+///
+/// the .env file should look like this:
+/// SUPABASE_URL=https://<your-supabase-url>.supabase.co
+/// SUPABASE_ANON_KEY=<your-supabase-anon-key>
+/// SUPABASE_KEY=<your-supabase-key>
+/// SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>
+///
+
 pub trait AppFlowyServer: Send + Sync + 'static {
   fn user_service(&self) -> Arc<dyn UserAuthService>;
+  fn folder_service(&self) -> Arc<dyn FolderCloudService>;
 }

+ 21 - 0
frontend/rust-lib/flowy-server/src/local_server/impls/folder.rs

@@ -0,0 +1,21 @@
+use flowy_error::FlowyError;
+use flowy_folder2::deps::{FolderCloudService, Workspace};
+use flowy_folder2::gen_workspace_id;
+use lib_infra::future::FutureResult;
+use lib_infra::util::timestamp;
+
+pub(crate) struct LocalServerFolderCloudServiceImpl();
+
+impl FolderCloudService for LocalServerFolderCloudServiceImpl {
+  fn create_workspace(&self, _uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
+    let name = name.to_string();
+    FutureResult::new(async move {
+      Ok(Workspace {
+        id: gen_workspace_id(),
+        name: name.to_string(),
+        belongings: Default::default(),
+        created_at: timestamp(),
+      })
+    })
+  }
+}

+ 5 - 0
frontend/rust-lib/flowy-server/src/local_server/impls/mod.rs

@@ -0,0 +1,5 @@
+mod folder;
+mod user;
+
+pub(crate) use folder::*;
+pub(crate) use user::*;

+ 0 - 0
frontend/rust-lib/flowy-server/src/local_server/user.rs → frontend/rust-lib/flowy-server/src/local_server/impls/user.rs


+ 1 - 1
frontend/rust-lib/flowy-server/src/local_server/mod.rs

@@ -1,5 +1,5 @@
 pub use server::*;
 
+pub mod impls;
 mod server;
 pub(crate) mod uid;
-mod user;

+ 8 - 1
frontend/rust-lib/flowy-server/src/local_server/server.rs

@@ -1,11 +1,14 @@
 use std::sync::Arc;
 
+use flowy_folder2::deps::FolderCloudService;
 use parking_lot::RwLock;
 use tokio::sync::mpsc;
 
 use flowy_user::event_map::UserAuthService;
 
-use crate::local_server::user::LocalServerUserAuthServiceImpl;
+use crate::local_server::impls::{
+  LocalServerFolderCloudServiceImpl, LocalServerUserAuthServiceImpl,
+};
 use crate::AppFlowyServer;
 
 #[derive(Default)]
@@ -31,4 +34,8 @@ impl AppFlowyServer for LocalServer {
   fn user_service(&self) -> Arc<dyn UserAuthService> {
     Arc::new(LocalServerUserAuthServiceImpl())
   }
+
+  fn folder_service(&self) -> Arc<dyn FolderCloudService> {
+    Arc::new(LocalServerFolderCloudServiceImpl())
+  }
 }

+ 21 - 0
frontend/rust-lib/flowy-server/src/self_host/impls/folder.rs

@@ -0,0 +1,21 @@
+use flowy_error::FlowyError;
+use flowy_folder2::deps::{FolderCloudService, Workspace};
+use flowy_folder2::gen_workspace_id;
+use lib_infra::future::FutureResult;
+use lib_infra::util::timestamp;
+
+pub(crate) struct SelfHostedServerFolderCloudServiceImpl();
+
+impl FolderCloudService for SelfHostedServerFolderCloudServiceImpl {
+  fn create_workspace(&self, _uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
+    let name = name.to_string();
+    FutureResult::new(async move {
+      Ok(Workspace {
+        id: gen_workspace_id(),
+        name: name.to_string(),
+        belongings: Default::default(),
+        created_at: timestamp(),
+      })
+    })
+  }
+}

+ 5 - 0
frontend/rust-lib/flowy-server/src/self_host/impls/mod.rs

@@ -0,0 +1,5 @@
+mod folder;
+mod user;
+
+pub(crate) use folder::*;
+pub(crate) use user::*;

+ 0 - 0
frontend/rust-lib/flowy-server/src/self_host/user.rs → frontend/rust-lib/flowy-server/src/self_host/impls/user.rs


+ 1 - 2
frontend/rust-lib/flowy-server/src/self_host/mod.rs

@@ -1,6 +1,5 @@
 pub use server::*;
-pub use user::*;
 
 pub mod configuration;
+pub mod impls;
 mod server;
-mod user;

+ 8 - 1
frontend/rust-lib/flowy-server/src/self_host/server.rs

@@ -1,9 +1,12 @@
+use flowy_folder2::deps::FolderCloudService;
 use std::sync::Arc;
 
 use flowy_user::event_map::UserAuthService;
 
 use crate::self_host::configuration::SelfHostedConfiguration;
-use crate::self_host::SelfHostedUserAuthServiceImpl;
+use crate::self_host::impls::{
+  SelfHostedServerFolderCloudServiceImpl, SelfHostedUserAuthServiceImpl,
+};
 use crate::AppFlowyServer;
 
 pub struct SelfHostServer {
@@ -20,4 +23,8 @@ impl AppFlowyServer for SelfHostServer {
   fn user_service(&self) -> Arc<dyn UserAuthService> {
     Arc::new(SelfHostedUserAuthServiceImpl::new(self.config.clone()))
   }
+
+  fn folder_service(&self) -> Arc<dyn FolderCloudService> {
+    Arc::new(SelfHostedServerFolderCloudServiceImpl())
+  }
 }

+ 54 - 0
frontend/rust-lib/flowy-server/src/supabase/impls/folder.rs

@@ -0,0 +1,54 @@
+use crate::supabase::request::create_workspace_with_uid;
+use flowy_error::FlowyError;
+use flowy_folder2::deps::{FolderCloudService, Workspace};
+use lib_infra::future::FutureResult;
+use postgrest::Postgrest;
+use std::sync::Arc;
+
+pub(crate) const WORKSPACE_TABLE: &str = "af_workspace";
+pub(crate) const WORKSPACE_NAME_COLUMN: &str = "workspace_name";
+pub(crate) struct SupabaseFolderCloudServiceImpl {
+  postgrest: Arc<Postgrest>,
+}
+
+impl FolderCloudService for SupabaseFolderCloudServiceImpl {
+  fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, FlowyError> {
+    let name = name.to_string();
+    let postgrest = self.postgrest.clone();
+    FutureResult::new(async move { create_workspace_with_uid(postgrest, uid, &name).await })
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use crate::supabase::request::{
+    create_user_with_uuid, create_workspace_with_uid, get_user_workspace_with_uid,
+  };
+  use crate::supabase::{SupabaseConfiguration, SupabaseServer};
+  use dotenv::dotenv;
+  use std::sync::Arc;
+
+  #[tokio::test]
+  async fn create_user_workspace() {
+    dotenv().ok();
+    if let Ok(config) = SupabaseConfiguration::from_env() {
+      let server = Arc::new(SupabaseServer::new(config));
+      let uuid = uuid::Uuid::new_v4();
+      let uid = create_user_with_uuid(server.postgres.clone(), uuid.to_string())
+        .await
+        .unwrap()
+        .uid;
+
+      create_workspace_with_uid(server.postgres.clone(), uid, "test")
+        .await
+        .unwrap();
+
+      let workspaces = get_user_workspace_with_uid(server.postgres.clone(), uid)
+        .await
+        .unwrap();
+      assert_eq!(workspaces.len(), 2);
+      assert_eq!(workspaces[0].name, "My workspace");
+      assert_eq!(workspaces[1].name, "test");
+    }
+  }
+}

+ 5 - 0
frontend/rust-lib/flowy-server/src/supabase/impls/mod.rs

@@ -0,0 +1,5 @@
+mod folder;
+mod user;
+
+pub(crate) use folder::*;
+pub(crate) use user::*;

+ 27 - 27
frontend/rust-lib/flowy-server/src/supabase/user.rs → frontend/rust-lib/flowy-server/src/supabase/impls/user.rs

@@ -12,7 +12,8 @@ use crate::supabase::request::*;
 
 pub(crate) const USER_TABLE: &str = "af_user";
 pub(crate) const USER_PROFILE_TABLE: &str = "af_user_profile";
-pub(crate) const USER_WORKSPACE_TABLE: &str = "af_user_workspace_view";
+#[allow(dead_code)]
+pub(crate) const USER_WORKSPACE_TABLE: &str = "af_workspace";
 pub(crate) struct PostgrestUserAuthServiceImpl {
   postgrest: Arc<Postgrest>,
 }
@@ -41,14 +42,12 @@ impl UserAuthService for PostgrestUserAuthServiceImpl {
     let postgrest = self.postgrest.clone();
     FutureResult::new(async move {
       let uuid = uuid_from_box_any(params)?;
-      match get_user_workspace_with_uuid(postgrest, uuid).await? {
-        None => Err(FlowyError::user_not_exist()),
-        Some(user) => Ok(SignInResponse {
-          user_id: user.uid,
-          workspace_id: user.workspace_id,
-          ..Default::default()
-        }),
-      }
+      let user_profile = get_user_profile(postgrest, GetUserProfileParams::Uuid(uuid)).await?;
+      Ok(SignInResponse {
+        user_id: user_profile.uid,
+        workspace_id: user_profile.workspace_id,
+        ..Default::default()
+      })
     })
   }
 
@@ -76,18 +75,19 @@ impl UserAuthService for PostgrestUserAuthServiceImpl {
   ) -> FutureResult<Option<UserProfile>, FlowyError> {
     let postgrest = self.postgrest.clone();
     FutureResult::new(async move {
-      let profile = get_user_workspace_with_uid(postgrest, uid)
-        .await?
-        .map(|user_workspace| UserProfile {
-          id: user_workspace.uid,
-          email: "".to_string(),
-          name: user_workspace.name,
-          token: "".to_string(),
-          icon_url: "".to_string(),
-          openai_key: "".to_string(),
-          workspace_id: user_workspace.workspace_id,
-        });
-      Ok(profile)
+      let user_profile_resp = get_user_profile(postgrest, GetUserProfileParams::Uid(uid)).await?;
+
+      let profile = UserProfile {
+        id: user_profile_resp.uid,
+        email: user_profile_resp.email,
+        name: user_profile_resp.name,
+        token: "".to_string(),
+        icon_url: "".to_string(),
+        openai_key: "".to_string(),
+        workspace_id: user_profile_resp.workspace_id,
+      };
+
+      Ok(Some(profile))
     })
   }
 }
@@ -100,8 +100,10 @@ mod tests {
 
   use flowy_user::entities::UpdateUserProfileParams;
 
-  use crate::supabase::request::{get_user_profile, get_user_workspace_with_uid};
-  use crate::supabase::user::{create_user_with_uuid, get_user_id_with_uuid, update_user_profile};
+  use crate::supabase::request::{
+    create_user_with_uuid, get_user_id_with_uuid, get_user_profile, get_user_workspace_with_uid,
+    update_user_profile, GetUserProfileParams,
+  };
   use crate::supabase::{SupabaseConfiguration, SupabaseServer};
 
   #[tokio::test]
@@ -151,17 +153,15 @@ mod tests {
         .unwrap();
       println!("result: {:?}", result);
 
-      let result = get_user_profile(server.postgres.clone(), uid)
+      let result = get_user_profile(server.postgres.clone(), GetUserProfileParams::Uid(uid))
         .await
-        .unwrap()
         .unwrap();
       assert_eq!(result.name, "nathan".to_string());
 
       let result = get_user_workspace_with_uid(server.postgres.clone(), uid)
         .await
-        .unwrap()
         .unwrap();
-      assert!(!result.workspace_id.is_empty());
+      assert!(!result.is_empty());
     }
   }
 }

+ 1 - 1
frontend/rust-lib/flowy-server/src/supabase/mod.rs

@@ -1,7 +1,7 @@
 pub use server::*;
 
+pub mod impls;
 mod request;
 mod response;
 mod retry;
 mod server;
-pub mod user;

+ 87 - 38
frontend/rust-lib/flowy-server/src/supabase/request.rs

@@ -5,13 +5,16 @@ use postgrest::Postgrest;
 use serde_json::json;
 
 use flowy_error::{ErrorCode, FlowyError};
+use flowy_folder2::deps::Workspace;
 use flowy_user::entities::UpdateUserProfileParams;
 use lib_infra::box_any::BoxAny;
 
+use crate::supabase::impls::{
+  USER_PROFILE_TABLE, USER_TABLE, USER_WORKSPACE_TABLE, WORKSPACE_NAME_COLUMN, WORKSPACE_TABLE,
+};
 use crate::supabase::response::{
-  InsertResponse, PostgrestError, UserProfile, UserProfileList, UserWorkspace, UserWorkspaceList,
+  InsertResponse, PostgrestError, UserProfileResponse, UserProfileResponseList, UserWorkspaceList,
 };
-use crate::supabase::user::{USER_PROFILE_TABLE, USER_TABLE, USER_WORKSPACE_TABLE};
 
 const USER_ID: &str = "uid";
 const USER_UUID: &str = "uuid";
@@ -19,13 +22,15 @@ const USER_UUID: &str = "uuid";
 pub(crate) async fn create_user_with_uuid(
   postgrest: Arc<Postgrest>,
   uuid: String,
-) -> Result<UserWorkspace, FlowyError> {
-  let insert = format!("{{\"{}\": \"{}\"}}", USER_UUID, &uuid);
+) -> Result<UserProfileResponse, FlowyError> {
+  let mut insert = serde_json::Map::new();
+  insert.insert(USER_UUID.to_string(), json!(&uuid));
+  let insert_query = serde_json::to_string(&insert).unwrap();
 
   // Create a new user with uuid.
   let resp = postgrest
     .from(USER_TABLE)
-    .insert(insert)
+    .insert(insert_query)
     .execute()
     .await
     .map_err(|e| FlowyError::new(ErrorCode::HttpError, e))?;
@@ -44,13 +49,7 @@ pub(crate) async fn create_user_with_uuid(
       .map_err(|e| FlowyError::serde().context(e))?
       .first_or_error()?;
 
-    match get_user_workspace_with_uid(postgrest, record.uid).await {
-      Ok(Some(user)) => Ok(user),
-      _ => Err(FlowyError::new(
-        ErrorCode::Internal,
-        "Failed to get user workspace",
-      )),
-    }
+    get_user_profile(postgrest, GetUserProfileParams::Uid(record.uid)).await
   } else {
     let err = serde_json::from_str::<PostgrestError>(&content)
       .map_err(|e| FlowyError::serde().context(e))?;
@@ -58,8 +57,8 @@ pub(crate) async fn create_user_with_uuid(
     // If there is a unique violation, try to get the user id with uuid. At this point, the user
     // should exist.
     if err.is_unique_violation() {
-      match get_user_workspace_with_uuid(postgrest, uuid).await {
-        Ok(Some(user)) => Ok(user),
+      match get_user_profile(postgrest, GetUserProfileParams::Uuid(uuid)).await {
+        Ok(user) => Ok(user),
         _ => Err(FlowyError::new(
           ErrorCode::Internal,
           "Failed to get user workspace",
@@ -112,14 +111,21 @@ pub(crate) fn uuid_from_box_any(any: BoxAny) -> Result<String, FlowyError> {
   Ok(uuid.to_string())
 }
 
-#[allow(dead_code)]
+pub enum GetUserProfileParams {
+  Uid(i64),
+  Uuid(String),
+}
+
 pub(crate) async fn get_user_profile(
   postgrest: Arc<Postgrest>,
-  uid: i64,
-) -> Result<Option<UserProfile>, FlowyError> {
-  let resp = postgrest
-    .from(USER_PROFILE_TABLE)
-    .eq(USER_ID, uid.to_string())
+  params: GetUserProfileParams,
+) -> Result<UserProfileResponse, FlowyError> {
+  let mut builder = postgrest.from(USER_PROFILE_TABLE);
+  match params {
+    GetUserProfileParams::Uid(uid) => builder = builder.eq(USER_ID, uid.to_string()),
+    GetUserProfileParams::Uuid(uuid) => builder = builder.eq(USER_UUID, uuid),
+  }
+  let resp = builder
     .select("*")
     .execute()
     .await
@@ -129,19 +135,35 @@ pub(crate) async fn get_user_profile(
     .text()
     .await
     .map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
-  let resp = serde_json::from_str::<UserProfileList>(&content)
-    .map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserProfileList failed"))?;
-  Ok(resp.0.first().cloned())
+  let mut user_profiles =
+    serde_json::from_str::<UserProfileResponseList>(&content).map_err(|_e| {
+      FlowyError::new(
+        ErrorCode::Serde,
+        "Deserialize UserProfileResponseList failed",
+      )
+    })?;
+  if user_profiles.0.is_empty() {
+    return Err(FlowyError::new(
+      ErrorCode::Internal,
+      "Failed to get user profile",
+    ));
+  }
+  Ok(user_profiles.0.remove(0))
 }
 
-pub(crate) async fn get_user_workspace_with_uuid(
+pub(crate) async fn create_workspace_with_uid(
   postgrest: Arc<Postgrest>,
-  uuid: String,
-) -> Result<Option<UserWorkspace>, FlowyError> {
+  uid: i64,
+  name: &str,
+) -> Result<Workspace, FlowyError> {
+  let mut insert = serde_json::Map::new();
+  insert.insert(USER_ID.to_string(), json!(uid));
+  insert.insert(WORKSPACE_NAME_COLUMN.to_string(), json!(name));
+  let insert_query = serde_json::to_string(&insert).unwrap();
+
   let resp = postgrest
-    .from(USER_WORKSPACE_TABLE)
-    .eq(USER_UUID, uuid)
-    .select("*")
+    .from(WORKSPACE_TABLE)
+    .insert(insert_query)
     .execute()
     .await
     .map_err(|e| FlowyError::new(ErrorCode::HttpError, e))?;
@@ -150,15 +172,31 @@ pub(crate) async fn get_user_workspace_with_uuid(
     .text()
     .await
     .map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
-  let resp = serde_json::from_str::<UserWorkspaceList>(&content)
-    .map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?;
-  Ok(resp.0.first().cloned())
+  let mut workspace_list = serde_json::from_str::<UserWorkspaceList>(&content)
+    .map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?
+    .into_inner();
+
+  debug_assert!(workspace_list.len() == 1);
+  if workspace_list.is_empty() {
+    return Err(FlowyError::new(
+      ErrorCode::Internal,
+      "Failed to create workspace",
+    ));
+  }
+  let user_workspace = workspace_list.remove(0);
+  Ok(Workspace {
+    id: user_workspace.workspace_id,
+    name: user_workspace.workspace_name,
+    belongings: Default::default(),
+    created_at: user_workspace.created_at.timestamp(),
+  })
 }
 
+#[allow(dead_code)]
 pub(crate) async fn get_user_workspace_with_uid(
   postgrest: Arc<Postgrest>,
   uid: i64,
-) -> Result<Option<UserWorkspace>, FlowyError> {
+) -> Result<Vec<Workspace>, FlowyError> {
   let resp = postgrest
     .from(USER_WORKSPACE_TABLE)
     .eq(USER_ID, uid.to_string())
@@ -171,16 +209,27 @@ pub(crate) async fn get_user_workspace_with_uid(
     .text()
     .await
     .map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
-  let resp = serde_json::from_str::<UserWorkspaceList>(&content)
-    .map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?;
-  Ok(resp.0.first().cloned())
+  let user_workspaces = serde_json::from_str::<UserWorkspaceList>(&content)
+    .map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserWorkspaceList failed"))?
+    .0;
+  Ok(
+    user_workspaces
+      .into_iter()
+      .map(|user_workspace| Workspace {
+        id: user_workspace.workspace_id,
+        name: user_workspace.workspace_name,
+        belongings: Default::default(),
+        created_at: user_workspace.created_at.timestamp(),
+      })
+      .collect(),
+  )
 }
 
 #[allow(dead_code)]
 pub(crate) async fn update_user_profile(
   postgrest: Arc<Postgrest>,
   params: UpdateUserProfileParams,
-) -> Result<Option<UserProfile>, FlowyError> {
+) -> Result<Option<UserProfileResponse>, FlowyError> {
   if params.is_empty() {
     return Err(FlowyError::new(
       ErrorCode::UnexpectedEmpty,
@@ -206,7 +255,7 @@ pub(crate) async fn update_user_profile(
     .await
     .map_err(|e| FlowyError::new(ErrorCode::UnexpectedEmpty, e))?;
 
-  let resp = serde_json::from_str::<UserProfileList>(&content)
+  let resp = serde_json::from_str::<UserProfileResponseList>(&content)
     .map_err(|_e| FlowyError::new(ErrorCode::Serde, "Deserialize UserProfileList failed"))?;
   Ok(resp.0.first().cloned())
 }

+ 17 - 4
frontend/rust-lib/flowy-server/src/supabase/response.rs

@@ -1,3 +1,4 @@
+use chrono::{DateTime, Utc};
 use serde::{Deserialize, Deserializer, Serialize};
 use serde_json::Value;
 use thiserror::Error;
@@ -56,27 +57,39 @@ pub(crate) struct InsertRecord {
 
 #[allow(dead_code)]
 #[derive(Debug, Deserialize, Clone)]
-pub(crate) struct UserProfile {
+pub(crate) struct UserProfileResponse {
   pub uid: i64,
   #[serde(deserialize_with = "deserialize_null_or_default")]
   pub name: String,
+
   #[serde(deserialize_with = "deserialize_null_or_default")]
   pub email: String,
+
+  #[serde(deserialize_with = "deserialize_null_or_default")]
+  pub workspace_id: String,
 }
 
 #[derive(Debug, Deserialize)]
-pub(crate) struct UserProfileList(pub Vec<UserProfile>);
+pub(crate) struct UserProfileResponseList(pub Vec<UserProfileResponse>);
 
 #[derive(Debug, Deserialize, Clone)]
 pub(crate) struct UserWorkspace {
+  #[allow(dead_code)]
   pub uid: i64,
   #[serde(deserialize_with = "deserialize_null_or_default")]
-  pub name: String,
+  pub workspace_name: String,
+  pub created_at: DateTime<Utc>,
   pub workspace_id: String,
 }
 
 #[derive(Debug, Deserialize)]
-pub(crate) struct UserWorkspaceList(pub Vec<UserWorkspace>);
+pub(crate) struct UserWorkspaceList(pub(crate) Vec<UserWorkspace>);
+
+impl UserWorkspaceList {
+  pub(crate) fn into_inner(self) -> Vec<UserWorkspace> {
+    self.0
+  }
+}
 
 /// Handles the case where the value is null. If the value is null, return the default value of the
 /// type. Otherwise, deserialize the value.

+ 24 - 3
frontend/rust-lib/flowy-server/src/supabase/server.rs

@@ -1,15 +1,21 @@
 use std::sync::Arc;
 
 use postgrest::Postgrest;
+use serde::Deserialize;
 
-use flowy_config::entities::{SUPABASE_JWT_SECRET, SUPABASE_KEY, SUPABASE_URL};
 use flowy_error::{ErrorCode, FlowyError};
+use flowy_folder2::deps::FolderCloudService;
 use flowy_user::event_map::UserAuthService;
 
-use crate::supabase::user::PostgrestUserAuthServiceImpl;
+use crate::supabase::impls::PostgrestUserAuthServiceImpl;
 use crate::AppFlowyServer;
 
-#[derive(Debug)]
+pub const SUPABASE_URL: &str = "SUPABASE_URL";
+pub const SUPABASE_ANON_KEY: &str = "SUPABASE_ANON_KEY";
+pub const SUPABASE_KEY: &str = "SUPABASE_KEY";
+pub const SUPABASE_JWT_SECRET: &str = "SUPABASE_JWT_SECRET";
+
+#[derive(Debug, Deserialize)]
 pub struct SupabaseConfiguration {
   /// The url of the supabase server.
   pub url: String,
@@ -20,6 +26,11 @@ pub struct SupabaseConfiguration {
 }
 
 impl SupabaseConfiguration {
+  /// Load the configuration from the environment variables.
+  /// SUPABASE_URL=https://<your-supabase-url>.supabase.co
+  /// SUPABASE_KEY=<your-supabase-key>
+  /// SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>
+  ///
   pub fn from_env() -> Result<Self, FlowyError> {
     Ok(Self {
       url: std::env::var(SUPABASE_URL)
@@ -31,6 +42,12 @@ impl SupabaseConfiguration {
       })?,
     })
   }
+
+  pub fn write_env(&self) {
+    std::env::set_var(SUPABASE_URL, &self.url);
+    std::env::set_var(SUPABASE_KEY, &self.key);
+    std::env::set_var(SUPABASE_JWT_SECRET, &self.jwt_secret);
+  }
 }
 
 pub struct SupabaseServer {
@@ -53,4 +70,8 @@ impl AppFlowyServer for SupabaseServer {
   fn user_service(&self) -> Arc<dyn UserAuthService> {
     Arc::new(PostgrestUserAuthServiceImpl::new(self.postgres.clone()))
   }
+
+  fn folder_service(&self) -> Arc<dyn FolderCloudService> {
+    todo!()
+  }
 }

+ 4 - 8
frontend/rust-lib/flowy-test/Cargo.toml

@@ -10,7 +10,6 @@ flowy-core = { path = "../flowy-core" }
 flowy-user = { path = "../flowy-user"}
 flowy-net = { path = "../flowy-net"}
 flowy-folder2 = { path = "../flowy-folder2", features = ["test_helper"] }
-#flowy-document= { path = "../flowy-document" }
 lib-dispatch = { path = "../lib-dispatch" }
 lib-ot = { path = "../../../shared-lib/lib-ot" }
 lib-infra = { path = "../../../shared-lib/lib-infra" }
@@ -19,21 +18,18 @@ flowy-server = { path = "../flowy-server" }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
 protobuf = {version = "2.28.0"}
-#claim = "0.5.0"
 tokio = { version = "1.26", features = ["full"]}
 futures-util = "0.3.26"
 thread-id = "3.3.0"
-log = "0.4"
 bytes = "1.4"
 nanoid = "0.4.0"
 tempdir = "0.3.7"
+tracing = { version = "0.1.27" }
+parking_lot = "0.12.1"
+dotenv = "0.15.0"
 
 [dev-dependencies]
-quickcheck = "1.0.3"
-quickcheck_macros = "0.9.1"
-fake = "2.5.0"
-futures = "0.3.26"
-serial_test = "0.5.1"
+uuid = { version = "1.3.3", features = ["v4"] }
 
 [features]
 dart = ["flowy-core/dart"]

+ 15 - 45
frontend/rust-lib/flowy-test/src/event_builder.rs

@@ -1,5 +1,5 @@
-use crate::FlowySDKTest;
-use flowy_user::{entities::UserProfilePB, errors::FlowyError};
+use crate::FlowyCoreTest;
+use flowy_user::errors::FlowyError;
 use lib_dispatch::prelude::{
   AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, StatusCode,
   ToBytes, *,
@@ -8,38 +8,18 @@ use std::{
   convert::TryFrom,
   fmt::{Debug, Display},
   hash::Hash,
-  marker::PhantomData,
   sync::Arc,
 };
 
-pub type Folder2EventBuilder = EventBuilder<FlowyError>;
-impl Folder2EventBuilder {
-  pub fn new(sdk: FlowySDKTest) -> Self {
-    EventBuilder::test(TestContext::new(sdk))
-  }
-  pub fn user_profile(&self) -> &Option<UserProfilePB> {
-    &self.user_profile
-  }
-}
-
-pub type UserModuleEventBuilder = Folder2EventBuilder;
-
 #[derive(Clone)]
-pub struct EventBuilder<E> {
+pub struct EventBuilder {
   context: TestContext,
-  user_profile: Option<UserProfilePB>,
-  err_phantom: PhantomData<E>,
 }
 
-impl<E> EventBuilder<E>
-where
-  E: AFPluginFromBytes + Debug,
-{
-  fn test(context: TestContext) -> Self {
+impl EventBuilder {
+  pub fn new(sdk: FlowyCoreTest) -> Self {
     Self {
-      context,
-      user_profile: None,
-      err_phantom: PhantomData,
+      context: TestContext::new(sdk),
     }
   }
 
@@ -53,7 +33,7 @@ where
         self.context.request = Some(module_request.payload(bytes))
       },
       Err(e) => {
-        log::error!("Set payload failed: {:?}", e);
+        tracing::error!("Set payload failed: {:?}", e);
       },
     }
     self
@@ -86,7 +66,7 @@ where
     R: AFPluginFromBytes,
   {
     let response = self.get_response();
-    match response.clone().parse::<R, E>() {
+    match response.clone().parse::<R, FlowyError>() {
       Ok(Ok(data)) => data,
       Ok(Err(e)) => {
         panic!(
@@ -105,22 +85,12 @@ where
     }
   }
 
-  pub fn error(self) -> E {
+  pub fn error(self) -> Option<FlowyError> {
     let response = self.get_response();
     assert_eq!(response.status_code, StatusCode::Err);
-    <AFPluginData<E>>::try_from(response.payload)
-      .unwrap()
-      .into_inner()
-  }
-
-  pub fn assert_error(self) -> Self {
-    // self.context.assert_error();
-    self
-  }
-
-  pub fn assert_success(self) -> Self {
-    // self.context.assert_success();
-    self
+    <AFPluginData<FlowyError>>::try_from(response.payload)
+      .ok()
+      .map(|data| data.into_inner())
   }
 
   fn dispatch(&self) -> Arc<AFPluginDispatcher> {
@@ -132,7 +102,7 @@ where
       .context
       .response
       .as_ref()
-      .expect("must call sync_send first")
+      .expect("must call sync_send/async_send first")
       .clone()
   }
 
@@ -143,13 +113,13 @@ where
 
 #[derive(Clone)]
 pub struct TestContext {
-  pub sdk: FlowySDKTest,
+  pub sdk: FlowyCoreTest,
   request: Option<AFPluginRequest>,
   response: Option<AFPluginEventResponse>,
 }
 
 impl TestContext {
-  pub fn new(sdk: FlowySDKTest) -> Self {
+  pub fn new(sdk: FlowyCoreTest) -> Self {
     Self {
       sdk,
       request: None,

+ 111 - 0
frontend/rust-lib/flowy-test/src/folder_event.rs

@@ -0,0 +1,111 @@
+use crate::event_builder::EventBuilder;
+use crate::FlowyCoreTest;
+use flowy_folder2::entities::*;
+use flowy_folder2::event_map::FolderEvent::*;
+
+pub struct ViewTest {
+  pub sdk: FlowyCoreTest,
+  pub workspace: WorkspacePB,
+  pub parent_view: ViewPB,
+  pub child_view: ViewPB,
+}
+
+impl ViewTest {
+  #[allow(dead_code)]
+  pub async fn new(sdk: &FlowyCoreTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
+    let workspace = create_workspace(sdk, "Workspace", "").await;
+    open_workspace(sdk, &workspace.id).await;
+    let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await;
+    let view = create_view(sdk, &app.id, layout, data).await;
+    Self {
+      sdk: sdk.clone(),
+      workspace,
+      parent_view: app,
+      child_view: view,
+    }
+  }
+
+  pub async fn new_grid_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
+    Self::new(sdk, ViewLayoutPB::Grid, data).await
+  }
+
+  pub async fn new_board_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
+    Self::new(sdk, ViewLayoutPB::Board, data).await
+  }
+
+  pub async fn new_calendar_view(sdk: &FlowyCoreTest, data: Vec<u8>) -> Self {
+    Self::new(sdk, ViewLayoutPB::Calendar, data).await
+  }
+
+  pub async fn new_document_view(sdk: &FlowyCoreTest) -> Self {
+    Self::new(sdk, ViewLayoutPB::Document, vec![]).await
+  }
+}
+
+async fn create_workspace(sdk: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
+  let request = CreateWorkspacePayloadPB {
+    name: name.to_owned(),
+    desc: desc.to_owned(),
+  };
+
+  EventBuilder::new(sdk.clone())
+    .event(CreateWorkspace)
+    .payload(request)
+    .async_send()
+    .await
+    .parse::<WorkspacePB>()
+}
+
+async fn open_workspace(sdk: &FlowyCoreTest, workspace_id: &str) {
+  let payload = WorkspaceIdPB {
+    value: Some(workspace_id.to_owned()),
+  };
+  let _ = EventBuilder::new(sdk.clone())
+    .event(OpenWorkspace)
+    .payload(payload)
+    .async_send()
+    .await;
+}
+
+async fn create_app(sdk: &FlowyCoreTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
+  let create_app_request = CreateViewPayloadPB {
+    belong_to_id: workspace_id.to_owned(),
+    name: name.to_string(),
+    desc: desc.to_string(),
+    thumbnail: None,
+    layout: ViewLayoutPB::Document,
+    initial_data: vec![],
+    ext: Default::default(),
+  };
+
+  EventBuilder::new(sdk.clone())
+    .event(CreateView)
+    .payload(create_app_request)
+    .async_send()
+    .await
+    .parse::<ViewPB>()
+}
+
+async fn create_view(
+  sdk: &FlowyCoreTest,
+  app_id: &str,
+  layout: ViewLayoutPB,
+  data: Vec<u8>,
+) -> ViewPB {
+  let payload = CreateViewPayloadPB {
+    belong_to_id: app_id.to_string(),
+    name: "View A".to_string(),
+    desc: "".to_string(),
+    thumbnail: Some("http://1.png".to_string()),
+    layout,
+    initial_data: data,
+    ext: Default::default(),
+  };
+
+  EventBuilder::new(sdk.clone())
+    .event(CreateView)
+    .payload(payload)
+    .async_send()
+    .await
+    .parse::<ViewPB>()
+}

+ 0 - 216
frontend/rust-lib/flowy-test/src/helper.rs

@@ -1,216 +0,0 @@
-use std::sync::Arc;
-
-use flowy_folder2::entities::{
-  CreateViewPayloadPB, CreateWorkspacePayloadPB, ViewLayoutPB, ViewPB, WorkspaceIdPB, WorkspacePB,
-};
-use flowy_folder2::event_map::FolderEvent::{CreateView, CreateWorkspace, OpenWorkspace};
-use flowy_user::entities::AuthTypePB;
-use flowy_user::{
-  entities::{SignInPayloadPB, SignUpPayloadPB, UserProfilePB},
-  errors::FlowyError,
-  event_map::UserEvent::{InitUser, SignIn, SignOut, SignUp},
-};
-use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
-
-use crate::prelude::*;
-
-pub struct ViewTest {
-  pub sdk: FlowySDKTest,
-  pub workspace: WorkspacePB,
-  pub parent_view: ViewPB,
-  pub child_view: ViewPB,
-}
-
-impl ViewTest {
-  #[allow(dead_code)]
-  pub async fn new(sdk: &FlowySDKTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
-    let workspace = create_workspace(sdk, "Workspace", "").await;
-    open_workspace(sdk, &workspace.id).await;
-    let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await;
-    let view = create_view(sdk, &app.id, layout, data).await;
-    Self {
-      sdk: sdk.clone(),
-      workspace,
-      parent_view: app,
-      child_view: view,
-    }
-  }
-
-  pub async fn new_grid_view(sdk: &FlowySDKTest, data: Vec<u8>) -> Self {
-    Self::new(sdk, ViewLayoutPB::Grid, data).await
-  }
-
-  pub async fn new_board_view(sdk: &FlowySDKTest, data: Vec<u8>) -> Self {
-    Self::new(sdk, ViewLayoutPB::Board, data).await
-  }
-
-  pub async fn new_calendar_view(sdk: &FlowySDKTest, data: Vec<u8>) -> Self {
-    Self::new(sdk, ViewLayoutPB::Calendar, data).await
-  }
-
-  pub async fn new_document_view(sdk: &FlowySDKTest) -> Self {
-    Self::new(sdk, ViewLayoutPB::Document, vec![]).await
-  }
-}
-
-async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB {
-  let request = CreateWorkspacePayloadPB {
-    name: name.to_owned(),
-    desc: desc.to_owned(),
-  };
-
-  Folder2EventBuilder::new(sdk.clone())
-    .event(CreateWorkspace)
-    .payload(request)
-    .async_send()
-    .await
-    .parse::<WorkspacePB>()
-}
-
-async fn open_workspace(sdk: &FlowySDKTest, workspace_id: &str) {
-  let payload = WorkspaceIdPB {
-    value: Some(workspace_id.to_owned()),
-  };
-  let _ = Folder2EventBuilder::new(sdk.clone())
-    .event(OpenWorkspace)
-    .payload(payload)
-    .async_send()
-    .await;
-}
-
-async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
-  let create_app_request = CreateViewPayloadPB {
-    belong_to_id: workspace_id.to_owned(),
-    name: name.to_string(),
-    desc: desc.to_string(),
-    thumbnail: None,
-    layout: ViewLayoutPB::Document,
-    initial_data: vec![],
-    ext: Default::default(),
-  };
-
-  Folder2EventBuilder::new(sdk.clone())
-    .event(CreateView)
-    .payload(create_app_request)
-    .async_send()
-    .await
-    .parse::<ViewPB>()
-}
-
-async fn create_view(
-  sdk: &FlowySDKTest,
-  app_id: &str,
-  layout: ViewLayoutPB,
-  data: Vec<u8>,
-) -> ViewPB {
-  let payload = CreateViewPayloadPB {
-    belong_to_id: app_id.to_string(),
-    name: "View A".to_string(),
-    desc: "".to_string(),
-    thumbnail: Some("http://1.png".to_string()),
-    layout,
-    initial_data: data,
-    ext: Default::default(),
-  };
-
-  Folder2EventBuilder::new(sdk.clone())
-    .event(CreateView)
-    .payload(payload)
-    .async_send()
-    .await
-    .parse::<ViewPB>()
-}
-
-pub fn random_email() -> String {
-  format!("{}@appflowy.io", nanoid!(20))
-}
-
-pub fn login_email() -> String {
-  "[email protected]".to_string()
-}
-
-pub fn login_password() -> String {
-  "HelloWorld!123".to_string()
-}
-
-pub struct SignUpContext {
-  pub user_profile: UserProfilePB,
-  pub password: String,
-}
-
-pub fn sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
-  let password = login_password();
-  let payload = SignUpPayloadPB {
-    email: random_email(),
-    name: "app flowy".to_string(),
-    password: password.clone(),
-    auth_type: AuthTypePB::Local,
-  }
-  .into_bytes()
-  .unwrap();
-
-  let request = AFPluginRequest::new(SignUp).payload(payload);
-  let user_profile = AFPluginDispatcher::sync_send(dispatch, request)
-    .parse::<UserProfilePB, FlowyError>()
-    .unwrap()
-    .unwrap();
-
-  SignUpContext {
-    user_profile,
-    password,
-  }
-}
-
-pub async fn async_sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
-  let password = login_password();
-  let email = random_email();
-  let payload = SignUpPayloadPB {
-    email,
-    name: "app flowy".to_string(),
-    password: password.clone(),
-    auth_type: AuthTypePB::Local,
-  }
-  .into_bytes()
-  .unwrap();
-
-  let request = AFPluginRequest::new(SignUp).payload(payload);
-  let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request)
-    .await
-    .parse::<UserProfilePB, FlowyError>()
-    .unwrap()
-    .unwrap();
-
-  // let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
-  SignUpContext {
-    user_profile,
-    password,
-  }
-}
-
-pub async fn init_user_setting(dispatch: Arc<AFPluginDispatcher>) {
-  let request = AFPluginRequest::new(InitUser);
-  let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await;
-}
-
-#[allow(dead_code)]
-fn sign_in(dispatch: Arc<AFPluginDispatcher>) -> UserProfilePB {
-  let payload = SignInPayloadPB {
-    email: login_email(),
-    password: login_password(),
-    name: "rust".to_owned(),
-    auth_type: AuthTypePB::Local,
-  }
-  .into_bytes()
-  .unwrap();
-
-  let request = AFPluginRequest::new(SignIn).payload(payload);
-  AFPluginDispatcher::sync_send(dispatch, request)
-    .parse::<UserProfilePB, FlowyError>()
-    .unwrap()
-    .unwrap()
-}
-
-#[allow(dead_code)]
-fn logout(dispatch: Arc<AFPluginDispatcher>) {
-  let _ = AFPluginDispatcher::sync_send(dispatch, AFPluginRequest::new(SignOut));
-}

+ 37 - 32
frontend/rust-lib/flowy-test/src/lib.rs

@@ -1,57 +1,62 @@
 use nanoid::nanoid;
+use parking_lot::RwLock;
 use std::env::temp_dir;
+use std::sync::Arc;
 
 use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
-use flowy_user::entities::UserProfilePB;
-
-use crate::helper::*;
+use flowy_user::entities::{AuthTypePB, UserProfilePB};
 
+use crate::user_event::{async_sign_up, init_user_setting, SignUpContext};
 pub mod event_builder;
-pub mod helper;
-
-pub mod prelude {
-  pub use lib_dispatch::prelude::*;
-
-  pub use crate::{event_builder::*, helper::*, *};
-}
+pub mod folder_event;
+pub mod user_event;
 
 #[derive(Clone)]
-pub struct FlowySDKTest {
-  pub inner: AppFlowyCore,
+pub struct FlowyCoreTest {
+  auth_type: Arc<RwLock<AuthTypePB>>,
+  inner: AppFlowyCore,
 }
 
-impl std::ops::Deref for FlowySDKTest {
-  type Target = AppFlowyCore;
-
-  fn deref(&self) -> &Self::Target {
-    &self.inner
-  }
-}
-
-impl std::default::Default for FlowySDKTest {
+impl Default for FlowyCoreTest {
   fn default() -> Self {
-    Self::new()
+    let temp_dir = temp_dir();
+    let config =
+      AppFlowyCoreConfig::new(temp_dir.to_str().unwrap(), nanoid!(6)).log_filter("info", vec![]);
+    let inner = std::thread::spawn(|| AppFlowyCore::new(config))
+      .join()
+      .unwrap();
+    let auth_type = Arc::new(RwLock::new(AuthTypePB::Local));
+    std::mem::forget(inner.dispatcher());
+    Self { inner, auth_type }
   }
 }
 
-impl FlowySDKTest {
+impl FlowyCoreTest {
   pub fn new() -> Self {
-    let config =
-      AppFlowyCoreConfig::new(temp_dir().to_str().unwrap(), nanoid!(6)).log_filter("info", vec![]);
-    let sdk = std::thread::spawn(|| AppFlowyCore::new(config))
-      .join()
-      .unwrap();
-    std::mem::forget(sdk.dispatcher());
-    Self { inner: sdk }
+    Self::default()
   }
 
   pub async fn sign_up(&self) -> SignUpContext {
-    async_sign_up(self.inner.dispatcher()).await
+    let auth_type = self.auth_type.read().clone();
+    async_sign_up(self.inner.dispatcher(), auth_type).await
+  }
+
+  pub fn set_auth_type(&self, auth_type: AuthTypePB) {
+    *self.auth_type.write() = auth_type;
   }
 
   pub async fn init_user(&self) -> UserProfilePB {
-    let context = async_sign_up(self.inner.dispatcher()).await;
+    let auth_type = self.auth_type.read().clone();
+    let context = async_sign_up(self.inner.dispatcher(), auth_type).await;
     init_user_setting(self.inner.dispatcher()).await;
     context.user_profile
   }
 }
+
+impl std::ops::Deref for FlowyCoreTest {
+  type Target = AppFlowyCore;
+
+  fn deref(&self) -> &Self::Target {
+    &self.inner
+  }
+}

+ 103 - 0
frontend/rust-lib/flowy-test/src/user_event.rs

@@ -0,0 +1,103 @@
+use flowy_user::entities::{AuthTypePB, SignInPayloadPB, SignUpPayloadPB, UserProfilePB};
+use flowy_user::errors::FlowyError;
+use flowy_user::event_map::UserEvent::*;
+use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
+use nanoid::nanoid;
+use std::sync::Arc;
+
+pub fn random_email() -> String {
+  format!("{}@appflowy.io", nanoid!(20))
+}
+
+pub fn login_email() -> String {
+  "[email protected]".to_string()
+}
+
+pub fn login_password() -> String {
+  "HelloWorld!123".to_string()
+}
+
+pub struct SignUpContext {
+  pub user_profile: UserProfilePB,
+  pub password: String,
+}
+
+pub fn sign_up(dispatch: Arc<AFPluginDispatcher>) -> SignUpContext {
+  let password = login_password();
+  let payload = SignUpPayloadPB {
+    email: random_email(),
+    name: "app flowy".to_string(),
+    password: password.clone(),
+    auth_type: AuthTypePB::Local,
+  }
+  .into_bytes()
+  .unwrap();
+
+  let request = AFPluginRequest::new(SignUp).payload(payload);
+  let user_profile = AFPluginDispatcher::sync_send(dispatch, request)
+    .parse::<UserProfilePB, FlowyError>()
+    .unwrap()
+    .unwrap();
+
+  SignUpContext {
+    user_profile,
+    password,
+  }
+}
+
+pub async fn async_sign_up(
+  dispatch: Arc<AFPluginDispatcher>,
+  auth_type: AuthTypePB,
+) -> SignUpContext {
+  let password = login_password();
+  let email = random_email();
+  let payload = SignUpPayloadPB {
+    email,
+    name: "app flowy".to_string(),
+    password: password.clone(),
+    auth_type,
+  }
+  .into_bytes()
+  .unwrap();
+
+  let request = AFPluginRequest::new(SignUp).payload(payload);
+  let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request)
+    .await
+    .parse::<UserProfilePB, FlowyError>()
+    .unwrap()
+    .unwrap();
+
+  // let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
+  SignUpContext {
+    user_profile,
+    password,
+  }
+}
+
+pub async fn init_user_setting(dispatch: Arc<AFPluginDispatcher>) {
+  let request = AFPluginRequest::new(InitUser);
+  let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await;
+}
+
+#[allow(dead_code)]
+fn sign_in(dispatch: Arc<AFPluginDispatcher>) -> UserProfilePB {
+  let payload = SignInPayloadPB {
+    email: login_email(),
+    password: login_password(),
+    name: "rust".to_owned(),
+    auth_type: AuthTypePB::Local,
+  }
+  .into_bytes()
+  .unwrap();
+
+  let request = AFPluginRequest::new(SignIn).payload(payload);
+  AFPluginDispatcher::sync_send(dispatch, request)
+    .parse::<UserProfilePB, FlowyError>()
+    .unwrap()
+    .unwrap()
+}
+
+#[allow(dead_code)]
+fn logout(dispatch: Arc<AFPluginDispatcher>) {
+  let _ = AFPluginDispatcher::sync_send(dispatch, AFPluginRequest::new(SignOut));
+}

+ 1 - 0
frontend/rust-lib/flowy-test/tests/main.rs

@@ -0,0 +1 @@
+mod user;

+ 33 - 27
frontend/rust-lib/flowy-user/tests/event/auth_test.rs → frontend/rust-lib/flowy-test/tests/user/local_test/auth_test.rs

@@ -1,13 +1,15 @@
-use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
+use flowy_test::user_event::*;
+use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
 use flowy_user::entities::{AuthTypePB, SignInPayloadPB, SignUpPayloadPB, UserProfilePB};
-use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
+use flowy_user::errors::ErrorCode;
+use flowy_user::event_map::UserEvent::*;
 
-use crate::helper::*;
+use crate::user::local_test::helper::*;
 
 #[tokio::test]
 async fn sign_up_with_invalid_email() {
   for email in invalid_email_test_case() {
-    let sdk = FlowySDKTest::default();
+    let sdk = FlowyCoreTest::new();
     let request = SignUpPayloadPB {
       email: email.to_string(),
       name: valid_name(),
@@ -16,43 +18,45 @@ async fn sign_up_with_invalid_email() {
     };
 
     assert_eq!(
-      UserModuleEventBuilder::new(sdk)
+      EventBuilder::new(sdk)
         .event(SignUp)
         .payload(request)
         .async_send()
         .await
         .error()
+        .unwrap()
         .code,
       ErrorCode::EmailFormatInvalid.value()
     );
   }
 }
 #[tokio::test]
-async fn sign_up_with_invalid_password() {
-  for password in invalid_password_test_case() {
-    let sdk = FlowySDKTest::default();
-    let request = SignUpPayloadPB {
-      email: random_email(),
-      name: valid_name(),
-      password,
-      auth_type: AuthTypePB::Local,
-    };
+async fn sign_up_with_long_password() {
+  let sdk = FlowyCoreTest::new();
+  let request = SignUpPayloadPB {
+    email: random_email(),
+    name: valid_name(),
+    password: "1234".repeat(100).as_str().to_string(),
+    auth_type: AuthTypePB::Local,
+  };
 
-    UserModuleEventBuilder::new(sdk)
+  assert_eq!(
+    EventBuilder::new(sdk)
       .event(SignUp)
       .payload(request)
       .async_send()
       .await
-      .assert_error();
-  }
+      .error()
+      .unwrap()
+      .code,
+    ErrorCode::PasswordTooLong.value()
+  );
 }
 
 #[tokio::test]
 async fn sign_in_success() {
-  let test = FlowySDKTest::default();
-  let _ = UserModuleEventBuilder::new(test.clone())
-    .event(SignOut)
-    .sync_send();
+  let test = FlowyCoreTest::new();
+  let _ = EventBuilder::new(test.clone()).event(SignOut).sync_send();
   let sign_up_context = test.sign_up().await;
 
   let request = SignInPayloadPB {
@@ -62,7 +66,7 @@ async fn sign_in_success() {
     auth_type: AuthTypePB::Local,
   };
 
-  let response = UserModuleEventBuilder::new(test.clone())
+  let response = EventBuilder::new(test.clone())
     .event(SignIn)
     .payload(request)
     .async_send()
@@ -74,7 +78,7 @@ async fn sign_in_success() {
 #[tokio::test]
 async fn sign_in_with_invalid_email() {
   for email in invalid_email_test_case() {
-    let sdk = FlowySDKTest::default();
+    let sdk = FlowyCoreTest::new();
     let request = SignInPayloadPB {
       email: email.to_string(),
       password: login_password(),
@@ -83,12 +87,13 @@ async fn sign_in_with_invalid_email() {
     };
 
     assert_eq!(
-      UserModuleEventBuilder::new(sdk)
+      EventBuilder::new(sdk)
         .event(SignIn)
         .payload(request)
         .async_send()
         .await
         .error()
+        .unwrap()
         .code,
       ErrorCode::EmailFormatInvalid.value()
     );
@@ -98,7 +103,7 @@ async fn sign_in_with_invalid_email() {
 #[tokio::test]
 async fn sign_in_with_invalid_password() {
   for password in invalid_password_test_case() {
-    let sdk = FlowySDKTest::default();
+    let sdk = FlowyCoreTest::new();
 
     let request = SignInPayloadPB {
       email: random_email(),
@@ -107,11 +112,12 @@ async fn sign_in_with_invalid_password() {
       auth_type: AuthTypePB::Local,
     };
 
-    UserModuleEventBuilder::new(sdk)
+    assert!(EventBuilder::new(sdk)
       .event(SignIn)
       .payload(request)
       .async_send()
       .await
-      .assert_error();
+      .error()
+      .is_some())
   }
 }

+ 0 - 5
frontend/rust-lib/flowy-user/tests/event/helper.rs → frontend/rust-lib/flowy-test/tests/user/local_test/helper.rs

@@ -1,8 +1,3 @@
-pub use flowy_test::{
-  event_builder::*,
-  prelude::{login_password, random_email},
-};
-
 pub(crate) fn invalid_email_test_case() -> Vec<String> {
   // https://gist.github.com/cjaoude/fd9910626629b53c4d25
   vec![

+ 0 - 0
frontend/rust-lib/flowy-user/tests/event/main.rs → frontend/rust-lib/flowy-test/tests/user/local_test/mod.rs


+ 28 - 40
frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs → frontend/rust-lib/flowy-test/tests/user/local_test/user_profile_test.rs

@@ -1,5 +1,5 @@
-use crate::helper::*;
-use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
+use crate::user::local_test::helper::*;
+use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
 use flowy_user::entities::{UpdateUserProfilePayloadPB, UserProfilePB};
 use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
 use nanoid::nanoid;
@@ -8,20 +8,20 @@ use nanoid::nanoid;
 
 #[tokio::test]
 async fn user_profile_get_failed() {
-  let sdk = FlowySDKTest::default();
-  let result = UserModuleEventBuilder::new(sdk)
+  let sdk = FlowyCoreTest::new();
+  let result = EventBuilder::new(sdk)
     .event(GetUserProfile)
-    .assert_error()
     .async_send()
-    .await;
-  assert!(result.user_profile().is_none())
+    .await
+    .error();
+  assert!(result.is_some())
 }
 
 #[tokio::test]
 async fn user_profile_get() {
-  let test = FlowySDKTest::default();
+  let test = FlowyCoreTest::new();
   let user_profile = test.init_user().await;
-  let user = UserModuleEventBuilder::new(test.clone())
+  let user = EventBuilder::new(test.clone())
     .event(GetUserProfile)
     .sync_send()
     .parse::<UserProfilePB>();
@@ -30,18 +30,17 @@ async fn user_profile_get() {
 
 #[tokio::test]
 async fn user_update_with_name() {
-  let sdk = FlowySDKTest::default();
+  let sdk = FlowyCoreTest::new();
   let user = sdk.init_user().await;
   let new_name = "hello_world".to_owned();
   let request = UpdateUserProfilePayloadPB::new(user.id).name(&new_name);
-  let _ = UserModuleEventBuilder::new(sdk.clone())
+  let _ = EventBuilder::new(sdk.clone())
     .event(UpdateUserProfile)
     .payload(request)
     .sync_send();
 
-  let user_profile = UserModuleEventBuilder::new(sdk.clone())
+  let user_profile = EventBuilder::new(sdk.clone())
     .event(GetUserProfile)
-    .assert_error()
     .sync_send()
     .parse::<UserProfilePB>();
 
@@ -50,49 +49,35 @@ async fn user_update_with_name() {
 
 #[tokio::test]
 async fn user_update_with_email() {
-  let sdk = FlowySDKTest::default();
+  let sdk = FlowyCoreTest::new();
   let user = sdk.init_user().await;
   let new_email = format!("{}@gmail.com", nanoid!(6));
   let request = UpdateUserProfilePayloadPB::new(user.id).email(&new_email);
-  let _ = UserModuleEventBuilder::new(sdk.clone())
+  let _ = EventBuilder::new(sdk.clone())
     .event(UpdateUserProfile)
     .payload(request)
     .sync_send();
-  let user_profile = UserModuleEventBuilder::new(sdk.clone())
+  let user_profile = EventBuilder::new(sdk.clone())
     .event(GetUserProfile)
-    .assert_error()
     .sync_send()
     .parse::<UserProfilePB>();
 
   assert_eq!(user_profile.email, new_email,);
 }
 
-#[tokio::test]
-async fn user_update_with_password() {
-  let sdk = FlowySDKTest::default();
-  let user = sdk.init_user().await;
-  let new_password = "H123world!".to_owned();
-  let request = UpdateUserProfilePayloadPB::new(user.id).password(&new_password);
-
-  let _ = UserModuleEventBuilder::new(sdk.clone())
-    .event(UpdateUserProfile)
-    .payload(request)
-    .sync_send()
-    .assert_success();
-}
-
 #[tokio::test]
 async fn user_update_with_invalid_email() {
-  let test = FlowySDKTest::default();
+  let test = FlowyCoreTest::new();
   let user = test.init_user().await;
   for email in invalid_email_test_case() {
     let request = UpdateUserProfilePayloadPB::new(user.id).email(&email);
     assert_eq!(
-      UserModuleEventBuilder::new(test.clone())
+      EventBuilder::new(test.clone())
         .event(UpdateUserProfile)
         .payload(request)
         .sync_send()
         .error()
+        .unwrap()
         .code,
       ErrorCode::EmailFormatInvalid.value()
     );
@@ -101,27 +86,30 @@ async fn user_update_with_invalid_email() {
 
 #[tokio::test]
 async fn user_update_with_invalid_password() {
-  let test = FlowySDKTest::default();
+  let test = FlowyCoreTest::new();
   let user = test.init_user().await;
   for password in invalid_password_test_case() {
     let request = UpdateUserProfilePayloadPB::new(user.id).password(&password);
 
-    UserModuleEventBuilder::new(test.clone())
+    assert!(EventBuilder::new(test.clone())
       .event(UpdateUserProfile)
       .payload(request)
-      .sync_send()
-      .assert_error();
+      .async_send()
+      .await
+      .error()
+      .is_some());
   }
 }
 
 #[tokio::test]
 async fn user_update_with_invalid_name() {
-  let test = FlowySDKTest::default();
+  let test = FlowyCoreTest::new();
   let user = test.init_user().await;
   let request = UpdateUserProfilePayloadPB::new(user.id).name("");
-  UserModuleEventBuilder::new(test.clone())
+  assert!(EventBuilder::new(test.clone())
     .event(UpdateUserProfile)
     .payload(request)
     .sync_send()
-    .assert_error();
+    .error()
+    .is_some())
 }

+ 2 - 0
frontend/rust-lib/flowy-test/tests/user/mod.rs

@@ -0,0 +1,2 @@
+mod local_test;
+mod supabase_test;

+ 28 - 0
frontend/rust-lib/flowy-test/tests/user/supabase_test/auth_test.rs

@@ -0,0 +1,28 @@
+use crate::user::supabase_test::helper::get_supabase_config;
+
+use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
+use flowy_user::entities::{AuthTypePB, ThirdPartyAuthPB, UserProfilePB};
+
+use flowy_user::event_map::UserEvent::*;
+use std::collections::HashMap;
+
+#[tokio::test]
+async fn sign_up_test() {
+  if get_supabase_config().is_some() {
+    let test = FlowyCoreTest::new();
+    let mut map = HashMap::new();
+    map.insert("uuid".to_string(), uuid::Uuid::new_v4().to_string());
+    let payload = ThirdPartyAuthPB {
+      map,
+      auth_type: AuthTypePB::Supabase,
+    };
+
+    let response = EventBuilder::new(test.clone())
+      .event(ThirdPartyAuth)
+      .payload(payload)
+      .async_send()
+      .await
+      .parse::<UserProfilePB>();
+    dbg!(&response);
+  }
+}

+ 20 - 0
frontend/rust-lib/flowy-test/tests/user/supabase_test/helper.rs

@@ -0,0 +1,20 @@
+use dotenv::dotenv;
+use flowy_server::supabase::SupabaseConfiguration;
+
+/// In order to run this test, you need to create a .env file in the root directory of this project
+/// and add the following environment variables:
+/// - SUPABASE_URL
+/// - SUPABASE_ANON_KEY
+/// - SUPABASE_KEY
+/// - SUPABASE_JWT_SECRET
+///
+/// the .env file should look like this:
+/// SUPABASE_URL=https://<your-supabase-url>.supabase.co
+/// SUPABASE_ANON_KEY=<your-supabase-anon-key>
+/// SUPABASE_KEY=<your-supabase-key>
+/// SUPABASE_JWT_SECRET=<your-supabase-jwt-secret>
+///
+pub fn get_supabase_config() -> Option<SupabaseConfiguration> {
+  dotenv().ok()?;
+  SupabaseConfiguration::from_env().ok()
+}

+ 3 - 0
frontend/rust-lib/flowy-test/tests/user/supabase_test/mod.rs

@@ -0,0 +1,3 @@
+mod auth_test;
+mod helper;
+mod workspace_test;

+ 38 - 0
frontend/rust-lib/flowy-test/tests/user/supabase_test/workspace_test.rs

@@ -0,0 +1,38 @@
+use crate::user::supabase_test::helper::get_supabase_config;
+use flowy_folder2::entities::WorkspaceSettingPB;
+use flowy_folder2::event_map::FolderEvent::ReadCurrentWorkspace;
+
+use flowy_test::{event_builder::EventBuilder, FlowyCoreTest};
+use flowy_user::entities::{AuthTypePB, ThirdPartyAuthPB, UserProfilePB};
+
+use flowy_user::event_map::UserEvent::*;
+use std::collections::HashMap;
+
+#[tokio::test]
+async fn initial_workspace_test() {
+  if get_supabase_config().is_some() {
+    let test = FlowyCoreTest::new();
+    let mut map = HashMap::new();
+    map.insert("uuid".to_string(), uuid::Uuid::new_v4().to_string());
+    let payload = ThirdPartyAuthPB {
+      map,
+      auth_type: AuthTypePB::Supabase,
+    };
+
+    let _ = EventBuilder::new(test.clone())
+      .event(ThirdPartyAuth)
+      .payload(payload)
+      .async_send()
+      .await
+      .parse::<UserProfilePB>();
+
+    let workspace_settings = EventBuilder::new(test.clone())
+      .event(ReadCurrentWorkspace)
+      .async_send()
+      .await
+      .parse::<WorkspaceSettingPB>();
+
+    assert!(workspace_settings.latest_view.is_some());
+    dbg!(&workspace_settings);
+  }
+}

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

@@ -34,7 +34,6 @@ unicode-segmentation = "1.10"
 fancy-regex = "0.11.0"
 
 [dev-dependencies]
-flowy-test = { path = "../flowy-test" }
 nanoid = "0.4.0"
 fake = "2.0.0"
 rand = "0.8.4"

+ 1 - 0
frontend/rust-lib/flowy-user/src/event_handler.rs

@@ -16,6 +16,7 @@ pub async fn sign_in(
 ) -> DataResult<UserProfilePB, FlowyError> {
   let params: SignInParams = data.into_inner().try_into()?;
   let auth_type = params.auth_type.clone();
+
   let user_profile: UserProfilePB = session
     .sign_in(&auth_type, BoxAny::new(params))
     .await?

+ 5 - 0
frontend/rust-lib/flowy-user/src/event_map.rs

@@ -40,6 +40,7 @@ pub trait UserStatusCallback: Send + Sync + 'static {
 /// The user cloud service provider.
 /// The provider can be supabase, firebase, aws, or any other cloud service.
 pub trait UserCloudServiceProvider: Send + Sync + 'static {
+  fn set_auth_type(&self, auth_type: AuthType);
   fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError>;
 }
 
@@ -47,6 +48,10 @@ impl<T> UserCloudServiceProvider for Arc<T>
 where
   T: UserCloudServiceProvider,
 {
+  fn set_auth_type(&self, auth_type: AuthType) {
+    (**self).set_auth_type(auth_type)
+  }
+
   fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
     (**self).get_auth_service(auth_type)
   }

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

@@ -104,6 +104,7 @@ impl UserSession {
     auth_type: &AuthType,
     params: BoxAny,
   ) -> Result<UserProfile, FlowyError> {
+    self.cloud_services.set_auth_type(auth_type.clone());
     let resp = self
       .cloud_services
       .get_auth_service(auth_type)?
@@ -134,6 +135,7 @@ impl UserSession {
     auth_type: &AuthType,
     params: BoxAny,
   ) -> Result<UserProfile, FlowyError> {
+    self.cloud_services.set_auth_type(auth_type.clone());
     let resp = self
       .cloud_services
       .get_auth_service(auth_type)?
@@ -163,6 +165,7 @@ impl UserSession {
       .execute(&*(self.db_connection()?))?;
     self.database.close_user_db(session.user_id)?;
     self.set_session(None)?;
+
     let server = self.cloud_services.get_auth_service(auth_type)?;
     let token = session.token;
     let _ = tokio::spawn(async move {