Przeglądaj źródła

feat: integrate appflowy-cloud (#3359)

* feat: draft: code dependency

* chore: update ref

* feat: signup using client_api

* feat: support auto sign_in after sign_up if already confirmed(WIP)

* chore: update collab commit id

* chore: fix compile errors

* chore: user AFServer trait to provide optional service

* chore: refactor workspace

* chore: disable aws config

* chore: return ws connect

* chore: update collab rev

* chore: fmt and clippy

* chore: fix test

* chore: update chrono version

* chore: add script to update the collab crates commit id

* chore: update

---------

Co-authored-by: nathan <[email protected]>
Zack 1 rok temu
rodzic
commit
1c84ee1d53
94 zmienionych plików z 2052 dodań i 2346 usunięć
  1. 2 0
      .gitignore
  2. 382 109
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  3. 19 11
      frontend/appflowy_tauri/src-tauri/Cargo.toml
  4. 251 223
      frontend/rust-lib/Cargo.lock
  5. 44 10
      frontend/rust-lib/Cargo.toml
  6. 28 0
      frontend/rust-lib/collab-integrate/Cargo.toml
  7. 261 0
      frontend/rust-lib/collab-integrate/src/collab_builder.rs
  8. 70 0
      frontend/rust-lib/collab-integrate/src/config.rs
  9. 26 0
      frontend/rust-lib/collab-integrate/src/lib.rs
  10. 7 7
      frontend/rust-lib/dart-ffi/Cargo.toml
  11. 5 3
      frontend/rust-lib/flowy-config/Cargo.toml
  12. 22 21
      frontend/rust-lib/flowy-core/Cargo.toml
  13. 3 3
      frontend/rust-lib/flowy-core/src/deps_resolve/collab_deps.rs
  14. 2 2
      frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs
  15. 2 3
      frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs
  16. 2 2
      frontend/rust-lib/flowy-core/src/deps_resolve/folder_deps.rs
  17. 1 0
      frontend/rust-lib/flowy-core/src/integrate/mod.rs
  18. 54 312
      frontend/rust-lib/flowy-core/src/integrate/server.rs
  19. 321 0
      frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs
  20. 14 16
      frontend/rust-lib/flowy-core/src/lib.rs
  21. 0 2
      frontend/rust-lib/flowy-core/src/module.rs
  22. 1 1
      frontend/rust-lib/flowy-database-deps/Cargo.toml
  23. 7 7
      frontend/rust-lib/flowy-database2/Cargo.toml
  24. 2 2
      frontend/rust-lib/flowy-database2/src/manager.rs
  25. 1 1
      frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs
  26. 1 4
      frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs
  27. 3 2
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs
  28. 2 12
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs
  29. 1 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs
  30. 1 5
      frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs
  31. 1 1
      frontend/rust-lib/flowy-database2/src/services/group/controller_impls/date_controller.rs
  32. 1 1
      frontend/rust-lib/flowy-document-deps/Cargo.toml
  33. 5 5
      frontend/rust-lib/flowy-document2/Cargo.toml
  34. 2 2
      frontend/rust-lib/flowy-document2/src/manager.rs
  35. 6 8
      frontend/rust-lib/flowy-document2/tests/document/util.rs
  36. 4 2
      frontend/rust-lib/flowy-error/Cargo.toml
  37. 11 1
      frontend/rust-lib/flowy-error/src/code.rs
  38. 27 0
      frontend/rust-lib/flowy-error/src/impl_from/cloud.rs
  39. 2 0
      frontend/rust-lib/flowy-error/src/impl_from/mod.rs
  40. 1 1
      frontend/rust-lib/flowy-folder-deps/Cargo.toml
  41. 6 6
      frontend/rust-lib/flowy-folder2/Cargo.toml
  42. 2 2
      frontend/rust-lib/flowy-folder2/src/manager.rs
  43. 0 25
      frontend/rust-lib/flowy-net/Cargo.toml
  44. 0 3
      frontend/rust-lib/flowy-net/Flowy.toml
  45. 0 10
      frontend/rust-lib/flowy-net/build.rs
  46. 0 2
      frontend/rust-lib/flowy-net/src/entities/mod.rs
  47. 0 29
      frontend/rust-lib/flowy-net/src/entities/network_state.rs
  48. 0 5
      frontend/rust-lib/flowy-net/src/event_map.rs
  49. 0 3
      frontend/rust-lib/flowy-net/src/handlers/mod.rs
  50. 0 3
      frontend/rust-lib/flowy-net/src/lib.rs
  51. 1 1
      frontend/rust-lib/flowy-notification/Cargo.toml
  52. 1 1
      frontend/rust-lib/flowy-server-config/Cargo.toml
  53. 11 10
      frontend/rust-lib/flowy-server/Cargo.toml
  54. 1 37
      frontend/rust-lib/flowy-server/src/af_cloud/configuration.rs
  55. 7 2
      frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs
  56. 7 2
      frontend/rust-lib/flowy-server/src/af_cloud/impls/document.rs
  57. 12 18
      frontend/rust-lib/flowy-server/src/af_cloud/impls/folder.rs
  58. 47 106
      frontend/rust-lib/flowy-server/src/af_cloud/impls/user.rs
  59. 156 10
      frontend/rust-lib/flowy-server/src/af_cloud/server.rs
  60. 1 1
      frontend/rust-lib/flowy-server/src/lib.rs
  61. 0 6
      frontend/rust-lib/flowy-server/src/local_server/server.rs
  62. 12 1
      frontend/rust-lib/flowy-server/src/server.rs
  63. 22 22
      frontend/rust-lib/flowy-server/src/supabase/api/collab_storage.rs
  64. 1 1
      frontend/rust-lib/flowy-server/src/supabase/api/request.rs
  65. 6 3
      frontend/rust-lib/flowy-server/src/supabase/api/user.rs
  66. 1 1
      frontend/rust-lib/flowy-server/tests/main.rs
  67. 8 8
      frontend/rust-lib/flowy-server/tests/supabase_test/database_test.rs
  68. 21 21
      frontend/rust-lib/flowy-server/tests/supabase_test/folder_test.rs
  69. 1 1
      frontend/rust-lib/flowy-storage/Cargo.toml
  70. 12 12
      frontend/rust-lib/flowy-test/Cargo.toml
  71. 1 1
      frontend/rust-lib/flowy-test/tests/database/local_test/test.rs
  72. 1 1
      frontend/rust-lib/flowy-test/tests/database/supabase_test/helper.rs
  73. 2 1
      frontend/rust-lib/flowy-test/tests/user/supabase_test/auth_test.rs
  74. 3 3
      frontend/rust-lib/flowy-user-deps/Cargo.toml
  75. 10 10
      frontend/rust-lib/flowy-user/Cargo.toml
  76. 2 1
      frontend/rust-lib/flowy-user/src/event_handler.rs
  77. 3 2
      frontend/rust-lib/flowy-user/src/manager.rs
  78. 1 1
      frontend/rust-lib/flowy-user/src/migrations/historical_document.rs
  79. 1 1
      frontend/rust-lib/flowy-user/src/migrations/migrate_to_new_user.rs
  80. 1 1
      frontend/rust-lib/flowy-user/src/migrations/migration.rs
  81. 49 12
      frontend/rust-lib/flowy-user/src/migrations/sync_new_user.rs
  82. 1 1
      frontend/rust-lib/flowy-user/src/services/database.rs
  83. 1 1
      frontend/rust-lib/flowy-user/src/services/user_awareness.rs
  84. 12 4
      frontend/rust-lib/flowy-user/src/services/user_workspace.rs
  85. 30 0
      frontend/scripts/tool/update_collab_rev.sh
  86. 2 328
      shared-lib/Cargo.lock
  87. 0 1
      shared-lib/Cargo.toml
  88. 1 1
      shared-lib/lib-infra/Cargo.toml
  89. 0 32
      shared-lib/lib-ws/Cargo.toml
  90. 0 223
      shared-lib/lib-ws/src/connect.rs
  91. 0 101
      shared-lib/lib-ws/src/errors.rs
  92. 0 7
      shared-lib/lib-ws/src/lib.rs
  93. 0 46
      shared-lib/lib-ws/src/msg.rs
  94. 0 435
      shared-lib/lib-ws/src/ws.rs

+ 2 - 0
.gitignore

@@ -41,3 +41,5 @@ pubspec.lock
 # ignore the deb filegit
 frontend/package
 frontend/*.deb
+
+**/Cargo.toml.bak

+ 382 - 109
frontend/appflowy_tauri/src-tauri/Cargo.lock

@@ -70,6 +70,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
 dependencies = [
  "cfg-if",
+ "getrandom 0.2.10",
  "once_cell",
  "version_check",
 ]
@@ -107,6 +108,12 @@ dependencies = [
  "alloc-no-stdlib",
 ]
 
+[[package]]
+name = "allocator-api2"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+
 [[package]]
 name = "android-tzdata"
 version = "0.1.1"
@@ -137,33 +144,12 @@ version = "1.0.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
-[[package]]
-name = "appflowy-integrate"
-version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
-dependencies = [
- "anyhow",
- "collab",
- "collab-database",
- "collab-define",
- "collab-document",
- "collab-folder",
- "collab-persistence",
- "collab-plugins",
- "futures",
- "parking_lot",
- "serde",
- "serde_json",
- "tracing",
-]
-
 [[package]]
 name = "appflowy_tauri"
 version = "0.0.0"
 dependencies = [
  "bytes",
  "flowy-core",
- "flowy-net",
  "flowy-notification",
  "lib-dispatch",
  "serde",
@@ -243,6 +229,15 @@ dependencies = [
  "system-deps 6.1.1",
 ]
 
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 name = "atomic_refcell"
 version = "0.1.10"
@@ -367,7 +362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
 dependencies = [
  "borsh-derive",
- "hashbrown 0.13.2",
+ "hashbrown 0.12.3",
 ]
 
 [[package]]
@@ -433,6 +428,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
 dependencies = [
  "memchr",
+ "once_cell",
+ "regex-automata",
  "serde",
 ]
 
@@ -592,18 +589,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chrono"
-version = "0.4.26"
+version = "0.4.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "js-sys",
  "num-traits",
  "serde",
- "time 0.1.45",
  "wasm-bindgen",
- "winapi",
+ "windows-targets",
 ]
 
 [[package]]
@@ -671,6 +667,32 @@ dependencies = [
  "libloading",
 ]
 
+[[package]]
+name = "client-api"
+version = "0.1.0"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "collab-sync-protocol",
+ "futures-util",
+ "gotrue-entity",
+ "opener",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "shared_entity",
+ "storage-entity",
+ "thiserror",
+ "tokio",
+ "tokio-retry",
+ "tokio-stream",
+ "tokio-tungstenite",
+ "tracing",
+ "url",
+]
+
 [[package]]
 name = "cmd_lib"
 version = "1.3.0"
@@ -730,7 +752,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -749,7 +771,7 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -779,7 +801,7 @@ dependencies = [
 [[package]]
 name = "collab-define"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "anyhow",
  "collab",
@@ -791,7 +813,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -803,7 +825,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "anyhow",
  "collab",
@@ -823,7 +845,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "anyhow",
  "chrono",
@@ -840,10 +862,30 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "collab-integrate"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "collab",
+ "collab-database",
+ "collab-define",
+ "collab-document",
+ "collab-folder",
+ "collab-persistence",
+ "collab-plugins",
+ "futures",
+ "parking_lot",
+ "serde",
+ "serde_json",
+ "tracing",
+]
+
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "async-trait",
  "bincode",
@@ -864,7 +906,7 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -872,7 +914,6 @@ dependencies = [
  "collab-define",
  "collab-persistence",
  "collab-sync-protocol",
- "collab-ws",
  "futures-util",
  "lib0",
  "parking_lot",
@@ -893,10 +934,11 @@ dependencies = [
 [[package]]
 name = "collab-sync-protocol"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "bytes",
  "collab",
+ "collab-define",
  "md5",
  "serde",
  "serde_json",
@@ -907,7 +949,7 @@ dependencies = [
 [[package]]
 name = "collab-user"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
 dependencies = [
  "anyhow",
  "collab",
@@ -920,24 +962,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "collab-ws"
-version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
-dependencies = [
- "bytes",
- "collab-sync-protocol",
- "futures-util",
- "serde",
- "serde_json",
- "thiserror",
- "tokio",
- "tokio-retry",
- "tokio-stream",
- "tokio-tungstenite",
- "tracing",
-]
-
 [[package]]
 name = "color_quant"
 version = "1.1.0"
@@ -1036,6 +1060,21 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "crc"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484"
+
 [[package]]
 name = "crc32fast"
 version = "1.3.2"
@@ -1079,6 +1118,16 @@ dependencies = [
  "scopeguard",
 ]
 
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "crossbeam-utils"
 version = "0.8.16"
@@ -1315,6 +1364,12 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
 
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
 [[package]]
 name = "dtoa"
 version = "1.0.6"
@@ -1347,6 +1402,9 @@ name = "either"
 version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "embed-resource"
@@ -1418,6 +1476,12 @@ dependencies = [
  "backtrace",
 ]
 
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
 [[package]]
 name = "faccess"
 version = "0.2.4"
@@ -1522,7 +1586,7 @@ dependencies = [
  "console",
  "fancy-regex 0.10.0",
  "flowy-ast",
- "itertools",
+ "itertools 0.10.5",
  "lazy_static",
  "log",
  "phf 0.8.0",
@@ -1556,9 +1620,12 @@ dependencies = [
 name = "flowy-core"
 version = "0.1.0"
 dependencies = [
- "appflowy-integrate",
+ "anyhow",
  "bytes",
+ "collab",
  "collab-define",
+ "collab-integrate",
+ "collab-plugins",
  "diesel",
  "flowy-config",
  "flowy-database-deps",
@@ -1568,7 +1635,6 @@ dependencies = [
  "flowy-error",
  "flowy-folder-deps",
  "flowy-folder2",
- "flowy-net",
  "flowy-server",
  "flowy-server-config",
  "flowy-sqlite",
@@ -1604,7 +1670,6 @@ name = "flowy-database2"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "appflowy-integrate",
  "async-stream",
  "async-trait",
  "bytes",
@@ -1613,6 +1678,7 @@ dependencies = [
  "collab",
  "collab-database",
  "collab-define",
+ "collab-integrate",
  "csv",
  "dashmap",
  "fancy-regex 0.10.0",
@@ -1673,11 +1739,11 @@ name = "flowy-document2"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "appflowy-integrate",
  "bytes",
  "collab",
  "collab-define",
  "collab-document",
+ "collab-integrate",
  "flowy-codegen",
  "flowy-derive",
  "flowy-document-deps",
@@ -1719,6 +1785,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "bytes",
+ "client-api",
  "collab-database",
  "collab-document",
  "flowy-codegen",
@@ -1751,12 +1818,12 @@ dependencies = [
 name = "flowy-folder2"
 version = "0.1.0"
 dependencies = [
- "appflowy-integrate",
  "bytes",
  "chrono",
  "collab",
  "collab-define",
  "collab-folder",
+ "collab-integrate",
  "flowy-codegen",
  "flowy-derive",
  "flowy-error",
@@ -1776,17 +1843,6 @@ dependencies = [
  "uuid",
 ]
 
-[[package]]
-name = "flowy-net"
-version = "0.1.0"
-dependencies = [
- "bytes",
- "flowy-codegen",
- "lib-dispatch",
- "protobuf",
- "tracing",
-]
-
 [[package]]
 name = "flowy-notification"
 version = "0.1.0"
@@ -1808,6 +1864,7 @@ dependencies = [
  "anyhow",
  "bytes",
  "chrono",
+ "client-api",
  "collab",
  "collab-define",
  "collab-document",
@@ -1901,7 +1958,6 @@ name = "flowy-user"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "appflowy-integrate",
  "base64 0.21.2",
  "bytes",
  "chrono",
@@ -1910,6 +1966,7 @@ dependencies = [
  "collab-define",
  "collab-document",
  "collab-folder",
+ "collab-integrate",
  "collab-user",
  "diesel",
  "diesel_derives",
@@ -2045,6 +2102,17 @@ dependencies = [
  "futures-util",
 ]
 
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
 [[package]]
 name = "futures-io"
 version = "0.3.28"
@@ -2376,6 +2444,15 @@ dependencies = [
  "system-deps 6.1.1",
 ]
 
+[[package]]
+name = "gotrue-entity"
+version = "0.1.0"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "gtk"
 version = "0.15.5"
@@ -2473,6 +2550,19 @@ name = "hashbrown"
 version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+dependencies = [
+ "ahash 0.8.3",
+ "allocator-api2",
+]
+
+[[package]]
+name = "hashlink"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
+dependencies = [
+ "hashbrown 0.14.0",
+]
 
 [[package]]
 name = "heck"
@@ -2488,6 +2578,9 @@ name = "heck"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+dependencies = [
+ "unicode-segmentation",
+]
 
 [[package]]
 name = "hermit-abi"
@@ -2681,6 +2774,12 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "if_chain"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
+
 [[package]]
 name = "ignore"
 version = "0.4.20"
@@ -2785,6 +2884,15 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.8"
@@ -3338,6 +3446,15 @@ dependencies = [
  "minimal-lexical",
 ]
 
+[[package]]
+name = "normpath"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "nu-ansi-term"
 version = "0.46.0"
@@ -3468,6 +3585,17 @@ dependencies = [
  "windows-sys 0.42.0",
 ]
 
+[[package]]
+name = "opener"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788"
+dependencies = [
+ "bstr",
+ "normpath",
+ "winapi",
+]
+
 [[package]]
 name = "openssl"
 version = "0.10.55"
@@ -3595,6 +3723,12 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
 [[package]]
 name = "pathdiff"
 version = "0.2.1"
@@ -3863,7 +3997,7 @@ dependencies = [
  "line-wrap",
  "quick-xml",
  "serde",
- "time 0.3.22",
+ "time",
 ]
 
 [[package]]
@@ -4665,9 +4799,9 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.164"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
+checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
 dependencies = [
  "serde_derive",
 ]
@@ -4685,9 +4819,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.164"
+version = "1.0.188"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
+checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4696,9 +4830,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.99"
+version = "1.0.107"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
+checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
 dependencies = [
  "itoa 1.0.6",
  "ryu",
@@ -4707,9 +4841,9 @@ dependencies = [
 
 [[package]]
 name = "serde_repr"
-version = "0.1.12"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
+checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4750,7 +4884,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_with_macros",
- "time 0.3.22",
+ "time",
 ]
 
 [[package]]
@@ -4834,6 +4968,21 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "shared_entity"
+version = "0.1.0"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
+dependencies = [
+ "anyhow",
+ "opener",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "thiserror",
+ "url",
+]
+
 [[package]]
 name = "shlex"
 version = "1.1.0"
@@ -4966,6 +5115,99 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
+[[package]]
+name = "sqlformat"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
+dependencies = [
+ "itertools 0.11.0",
+ "nom 7.1.3",
+ "unicode_categories",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e58421b6bc416714d5115a2ca953718f6c621a51b68e4f4922aea5a4391a721"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53"
+dependencies = [
+ "ahash 0.8.3",
+ "atoi",
+ "byteorder",
+ "bytes",
+ "crc",
+ "crossbeam-queue",
+ "dotenvy",
+ "either",
+ "event-listener",
+ "futures-channel",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashlink",
+ "hex",
+ "indexmap 2.0.0",
+ "log",
+ "memchr",
+ "once_cell",
+ "paste",
+ "percent-encoding",
+ "serde",
+ "sha2",
+ "smallvec",
+ "sqlformat",
+ "thiserror",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "208e3165167afd7f3881b16c1ef3f2af69fa75980897aac8874a0696516d12c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc"
+dependencies = [
+ "dotenvy",
+ "either",
+ "heck 0.4.1",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx-core",
+ "syn 1.0.109",
+ "tempfile",
+ "url",
+]
+
 [[package]]
 name = "stable_deref_trait"
 version = "1.2.0"
@@ -4987,6 +5229,20 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "storage-entity"
+version = "0.1.0"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
+dependencies = [
+ "chrono",
+ "collab-define",
+ "serde",
+ "serde_json",
+ "sqlx",
+ "uuid",
+ "validator",
+]
+
 [[package]]
 name = "string_cache"
 version = "0.8.7"
@@ -5280,7 +5536,7 @@ dependencies = [
  "sha2",
  "tauri-utils",
  "thiserror",
- "time 0.3.22",
+ "time",
  "uuid",
  "walkdir",
 ]
@@ -5445,18 +5701,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
 
 [[package]]
 name = "thiserror"
-version = "1.0.40"
+version = "1.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.40"
+version = "1.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5483,17 +5739,6 @@ dependencies = [
  "once_cell",
 ]
 
-[[package]]
-name = "time"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
-dependencies = [
- "libc",
- "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi",
-]
-
 [[package]]
 name = "time"
 version = "0.3.22"
@@ -5978,6 +6223,12 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
 [[package]]
 name = "universal-hash"
 version = "0.5.1"
@@ -5996,9 +6247,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
 name = "url"
-version = "2.4.0"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
 dependencies = [
  "form_urlencoded",
  "idna",
@@ -6014,11 +6265,12 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
 
 [[package]]
 name = "uuid"
-version = "1.3.4"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81"
+checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
 dependencies = [
  "getrandom 0.2.10",
+ "serde",
  "sha1_smol",
 ]
 
@@ -6035,6 +6287,33 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "url",
+ "validator_derive",
+]
+
+[[package]]
+name = "validator_derive"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af"
+dependencies = [
+ "if_chain",
+ "lazy_static",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+ "validator_types",
+]
+
+[[package]]
+name = "validator_types"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3"
+dependencies = [
+ "proc-macro2",
+ "syn 1.0.109",
 ]
 
 [[package]]
@@ -6112,12 +6391,6 @@ version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
-[[package]]
-name = "wasi"
-version = "0.10.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
-
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"

+ 19 - 11
frontend/appflowy_tauri/src-tauri/Cargo.toml

@@ -23,7 +23,6 @@ tracing = { version = "0.1", features = ["log"] }
 lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = ["use_serde"] }
 flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite", "ts"] }
 flowy-notification = { path = "../../rust-lib/flowy-notification", features = ["ts"] }
-flowy-net = { path = "../../rust-lib/flowy-net" }
 
 [features]
 # by default Tauri runs in production mode
@@ -34,24 +33,33 @@ default = ["custom-protocol"]
 custom-protocol = ["tauri/custom-protocol"]
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
+client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
+
+# ⚠️⚠️⚠️
+# Please using the following command to update the revision id
+# Current directory: frontend
+# Run the script:
+# scripts/tool/update_collab_rev.sh  new_rev_id
+# ⚠️⚠️⚠️️
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-sync-protocol = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
 
 #collab = { path = "../../../../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../../../../AppFlowy-Collab/collab-folder" }
 #collab-document = { path = "../../../../AppFlowy-Collab/collab-document" }
 #collab-database = { path = "../../../../AppFlowy-Collab/collab-database" }
-#appflowy-integrate = { path = "../../../../AppFlowy-Collab/appflowy-integrate" }
 #collab-plugins = { path = "../../../../AppFlowy-Collab/collab-plugins" }
+#collab-persistence = { path = "../../../../AppFlowy-Collab/collab-persistence" }
 #collab-user = { path = "../../../../AppFlowy-Collab/collab-user" }
 #collab-define = { path = "../../../../AppFlowy-Collab/collab-define" }
+#collab-sync-protocol = { path = "../../../../AppFlowy-Collab/collab-sync-protocol" }
 
 
 

Plik diff jest za duży
+ 251 - 223
frontend/rust-lib/Cargo.lock


+ 44 - 10
frontend/rust-lib/Cargo.toml

@@ -2,7 +2,6 @@
 members = [
   "lib-dispatch",
   "lib-log",
-  "flowy-net",
   "flowy-core",
   "dart-ffi",
   "flowy-user",
@@ -23,8 +22,33 @@ members = [
   "flowy-config",
   "flowy-encrypt",
   "flowy-storage",
+  "collab-integrate",
 ]
 
+[workspace.dependencies]
+lib-dispatch = { workspace = true, path = "lib-dispatch" }
+lib-log = { workspace = true, path = "lib-log" }
+flowy-core = { workspace = true, path = "flowy-core" }
+dart-ffi = { workspace = true, path = "dart-ffi" }
+flowy-user = { workspace = true, path = "flowy-user" }
+flowy-user-deps = { workspace = true, path = "flowy-user-deps" }
+flowy-sqlite = { workspace = true, path = "flowy-sqlite" }
+flowy-folder2 = { workspace = true, path = "flowy-folder2" }
+flowy-folder-deps = { workspace = true, path = "flowy-folder-deps" }
+flowy-notification = { workspace = true, path = "flowy-notification" }
+flowy-document2 = { workspace = true, path = "flowy-document2" }
+flowy-document-deps = { workspace = true, path = "flowy-document-deps" }
+flowy-error = { workspace = true, path = "flowy-error" }
+flowy-database2 = { workspace = true, path = "flowy-database2" }
+flowy-database-deps = { workspace = true, path = "flowy-database-deps" }
+flowy-task = { workspace = true, path = "flowy-task" }
+flowy-server = { workspace = true, path = "flowy-server" }
+flowy-server-config = { workspace = true, path = "flowy-server-config" }
+flowy-config = { workspace = true, path = "flowy-config" }
+flowy-encrypt = { workspace = true, path = "flowy-encrypt" }
+flowy-storage = { workspace = true, path = "flowy-storage" }
+collab-integrate = { workspace = true, path = "collab-integrate" }
+
 [profile.dev]
 opt-level = 0
 lto = false
@@ -49,20 +73,30 @@ lto = false
 incremental = false
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
-collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
+client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
+
+# ⚠️⚠️⚠️
+# Please using the following command to update the revision id
+# Current directory: frontend
+# Run the script:
+# scripts/tool/update_collab_rev.sh  new_rev_id
+# ⚠️⚠️⚠️️
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-sync-protocol = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
 
 #collab = { path = "../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
 #collab-database= { path = "../AppFlowy-Collab/collab-database" }
 #collab-document = { path = "../AppFlowy-Collab/collab-document" }
 #collab-plugins = { path = "../AppFlowy-Collab/collab-plugins" }
-#appflowy-integrate = { path = "../AppFlowy-Collab/appflowy-integrate" }
+#collab-persistence = { path = "../AppFlowy-Collab/collab-persistence" }
 #collab-user = { path = "../AppFlowy-Collab/collab-user" }
 #collab-define = { path = "../AppFlowy-Collab/collab-define" }
+#collab-sync-protocol = { path = "../AppFlowy-Collab/collab-sync-protocol" }

+ 28 - 0
frontend/rust-lib/collab-integrate/Cargo.toml

@@ -0,0 +1,28 @@
+[package]
+name = "collab-integrate"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+collab = { version = "0.1.0" }
+collab-persistence = { version = "0.1.0", features = ["rocksdb_persistence"] }
+collab-folder = { version = "0.1.0" }
+collab-database = { version = "0.1.0" }
+collab-plugins = { version = "0.1.0" }
+collab-document = { version = "0.1.0" }
+collab-define = { version = "0.1.0" }
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+anyhow = "1.0"
+tracing = "0.1"
+parking_lot = "0.12.1"
+futures = "0.3"
+async-trait = "0.1.73"
+
+[features]
+default = []
+supabase_integrate = ["collab-plugins/postgres_storage_plugin", "collab-plugins/rocksdb_plugin"]
+appflowy_cloud_integrate = ["collab-plugins/sync_plugin", "collab-plugins/rocksdb_plugin"]
+snapshot_plugin = ["collab-plugins/snapshot_plugin"]

+ 261 - 0
frontend/rust-lib/collab-integrate/src/collab_builder.rs

@@ -0,0 +1,261 @@
+use std::fmt::Debug;
+use std::sync::{Arc, Weak};
+
+use anyhow::Error;
+use async_trait::async_trait;
+use collab::core::collab::{CollabRawData, MutexCollab};
+use collab::preclude::{CollabBuilder, CollabPlugin};
+use collab_define::{CollabObject, CollabType};
+use collab_persistence::kv::rocks_kv::RocksCollabDB;
+use collab_plugins::cloud_storage::network_state::{CollabNetworkReachability, CollabNetworkState};
+use collab_plugins::local_storage::rocksdb::RocksdbDiskPlugin;
+use collab_plugins::local_storage::CollabPersistenceConfig;
+use collab_plugins::snapshot::{CollabSnapshotPlugin, SnapshotPersistence};
+use futures::executor::block_on;
+use parking_lot::{Mutex, RwLock};
+
+#[derive(Clone, Debug)]
+pub enum CollabSource {
+  Local,
+  AFCloud,
+  Supabase,
+}
+
+pub enum CollabPluginContext {
+  Local,
+  AppFlowyCloud {
+    uid: i64,
+    collab_object: CollabObject,
+    local_collab: Weak<MutexCollab>,
+  },
+  Supabase {
+    uid: i64,
+    collab_object: CollabObject,
+    local_collab: Weak<MutexCollab>,
+    local_collab_db: Weak<RocksCollabDB>,
+  },
+}
+
+#[async_trait]
+pub trait CollabStorageProvider: Send + Sync + 'static {
+  fn storage_source(&self) -> CollabSource;
+
+  async fn get_plugins(
+    &self,
+    context: CollabPluginContext,
+  ) -> Vec<Arc<dyn collab::core::collab_plugin::CollabPlugin>>;
+
+  fn is_sync_enabled(&self) -> bool;
+}
+
+#[async_trait]
+impl<T> CollabStorageProvider for Arc<T>
+where
+  T: CollabStorageProvider,
+{
+  fn storage_source(&self) -> CollabSource {
+    (**self).storage_source()
+  }
+
+  async fn get_plugins(&self, context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
+    (**self).get_plugins(context).await
+  }
+
+  fn is_sync_enabled(&self) -> bool {
+    (**self).is_sync_enabled()
+  }
+}
+
+pub struct AppFlowyCollabBuilder {
+  network_reachability: CollabNetworkReachability,
+  workspace_id: RwLock<Option<String>>,
+  cloud_storage: RwLock<Arc<dyn CollabStorageProvider>>,
+  snapshot_persistence: Mutex<Option<Arc<dyn SnapshotPersistence>>>,
+  device_id: Mutex<String>,
+}
+
+impl AppFlowyCollabBuilder {
+  pub fn new<T: CollabStorageProvider>(storage_provider: T) -> Self {
+    Self {
+      network_reachability: CollabNetworkReachability::new(),
+      workspace_id: Default::default(),
+      cloud_storage: RwLock::new(Arc::new(storage_provider)),
+      snapshot_persistence: Default::default(),
+      device_id: Default::default(),
+    }
+  }
+
+  pub fn set_snapshot_persistence(&self, snapshot_persistence: Arc<dyn SnapshotPersistence>) {
+    *self.snapshot_persistence.lock() = Some(snapshot_persistence);
+  }
+
+  pub fn initialize(&self, workspace_id: String) {
+    *self.workspace_id.write() = Some(workspace_id);
+  }
+
+  pub fn set_sync_device(&self, device_id: String) {
+    *self.device_id.lock() = device_id;
+  }
+
+  pub fn update_network(&self, reachable: bool) {
+    if reachable {
+      self
+        .network_reachability
+        .set_state(CollabNetworkState::Connected)
+    } else {
+      self
+        .network_reachability
+        .set_state(CollabNetworkState::Disconnected)
+    }
+  }
+
+  fn collab_object(
+    &self,
+    uid: i64,
+    object_id: &str,
+    collab_type: CollabType,
+  ) -> Result<CollabObject, Error> {
+    let workspace_id = self.workspace_id.read().clone().ok_or_else(|| {
+      anyhow::anyhow!("When using supabase plugin, the workspace_id should not be empty")
+    })?;
+    Ok(CollabObject::new(
+      uid,
+      object_id.to_string(),
+      collab_type,
+      workspace_id,
+      self.device_id.lock().clone(),
+    ))
+  }
+
+  /// Creates a new collaboration builder with the default configuration.
+  ///
+  /// This function will initiate the creation of a [MutexCollab] object if it does not already exist.
+  /// To check for the existence of the object prior to creation, you should utilize a transaction
+  /// returned by the [read_txn] method of the [RocksCollabDB]. Then, invoke the [is_exist] method
+  /// to confirm the object's presence.
+  ///
+  /// # Parameters
+  /// - `uid`: The user ID associated with the collaboration.
+  /// - `object_id`: A string reference representing the ID of the object.
+  /// - `object_type`: The type of the collaboration, defined by the [CollabType] enum.
+  /// - `raw_data`: The raw data of the collaboration object, defined by the [CollabRawData] type.
+  /// - `collab_db`: A weak reference to the [RocksCollabDB].
+  ///
+  pub fn build(
+    &self,
+    uid: i64,
+    object_id: &str,
+    object_type: CollabType,
+    raw_data: CollabRawData,
+    collab_db: Weak<RocksCollabDB>,
+  ) -> Result<Arc<MutexCollab>, Error> {
+    self.build_with_config(
+      uid,
+      object_id,
+      object_type,
+      collab_db,
+      raw_data,
+      &CollabPersistenceConfig::default(),
+    )
+  }
+
+  /// Creates a new collaboration builder with the custom configuration.
+  ///
+  /// This function will initiate the creation of a [MutexCollab] object if it does not already exist.
+  /// To check for the existence of the object prior to creation, you should utilize a transaction
+  /// returned by the [read_txn] method of the [RocksCollabDB]. Then, invoke the [is_exist] method
+  /// to confirm the object's presence.
+  ///
+  /// # Parameters
+  /// - `uid`: The user ID associated with the collaboration.
+  /// - `object_id`: A string reference representing the ID of the object.
+  /// - `object_type`: The type of the collaboration, defined by the [CollabType] enum.
+  /// - `raw_data`: The raw data of the collaboration object, defined by the [CollabRawData] type.
+  /// - `collab_db`: A weak reference to the [RocksCollabDB].
+  ///
+  pub fn build_with_config(
+    &self,
+    uid: i64,
+    object_id: &str,
+    object_type: CollabType,
+    collab_db: Weak<RocksCollabDB>,
+    collab_raw_data: CollabRawData,
+    config: &CollabPersistenceConfig,
+  ) -> Result<Arc<MutexCollab>, Error> {
+    let collab = Arc::new(
+      CollabBuilder::new(uid, object_id)
+        .with_raw_data(collab_raw_data)
+        .with_plugin(RocksdbDiskPlugin::new_with_config(
+          uid,
+          collab_db.clone(),
+          config.clone(),
+        ))
+        .with_device_id(self.device_id.lock().clone())
+        .build()?,
+    );
+    {
+      let cloud_storage = self.cloud_storage.read();
+      let cloud_storage_type = cloud_storage.storage_source();
+      let collab_object = self.collab_object(uid, object_id, object_type)?;
+      match cloud_storage_type {
+        CollabSource::AFCloud => {
+          #[cfg(feature = "appflowy_cloud_integrate")]
+          {
+            //
+          }
+        },
+        CollabSource::Supabase => {
+          #[cfg(feature = "supabase_integrate")]
+          {
+            let local_collab = Arc::downgrade(&collab);
+            let local_collab_db = collab_db.clone();
+            let plugins = block_on(cloud_storage.get_plugins(CollabPluginContext::Supabase {
+              uid,
+              collab_object: collab_object.clone(),
+              local_collab,
+              local_collab_db,
+            }));
+            for plugin in plugins {
+              collab.lock().add_plugin(plugin);
+            }
+          }
+        },
+        CollabSource::Local => {},
+      }
+
+      if let Some(snapshot_persistence) = self.snapshot_persistence.lock().as_ref() {
+        if config.enable_snapshot {
+          let snapshot_plugin = CollabSnapshotPlugin::new(
+            uid,
+            collab_object,
+            snapshot_persistence.clone(),
+            collab_db,
+            config.snapshot_per_update,
+          );
+          // tracing::trace!("add snapshot plugin: {}", object_id);
+          collab.lock().add_plugin(Arc::new(snapshot_plugin));
+        }
+      }
+    }
+
+    block_on(collab.async_initialize());
+    Ok(collab)
+  }
+}
+
+pub struct DefaultCollabStorageProvider();
+
+#[async_trait]
+impl CollabStorageProvider for DefaultCollabStorageProvider {
+  fn storage_source(&self) -> CollabSource {
+    CollabSource::Local
+  }
+
+  async fn get_plugins(&self, _context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
+    vec![]
+  }
+
+  fn is_sync_enabled(&self) -> bool {
+    false
+  }
+}

+ 70 - 0
frontend/rust-lib/collab-integrate/src/config.rs

@@ -0,0 +1,70 @@
+use std::str::FromStr;
+
+use serde::{Deserialize, Serialize};
+
+pub enum CollabDBPluginProvider {
+  AWS,
+  Supabase,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, Default)]
+pub struct CollabPluginConfig {
+  /// Only one of the following two fields should be set.
+  aws_config: Option<AWSDynamoDBConfig>,
+}
+
+impl CollabPluginConfig {
+  pub fn from_env() -> Self {
+    let aws_config = AWSDynamoDBConfig::from_env();
+    Self { aws_config }
+  }
+
+  pub fn aws_config(&self) -> Option<&AWSDynamoDBConfig> {
+    self.aws_config.as_ref()
+  }
+}
+
+impl CollabPluginConfig {}
+
+impl FromStr for CollabPluginConfig {
+  type Err = serde_json::Error;
+
+  fn from_str(s: &str) -> Result<Self, Self::Err> {
+    serde_json::from_str(s)
+  }
+}
+
+pub const AWS_ACCESS_KEY_ID: &str = "AWS_ACCESS_KEY_ID";
+pub const AWS_SECRET_ACCESS_KEY: &str = "AWS_SECRET_ACCESS_KEY";
+pub const AWS_REGION: &str = "AWS_REGION";
+
+// To enable this test, you should set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your environment variables.
+// or create the ~/.aws/credentials file following the instructions in https://docs.aws.amazon.com/sdk-for-rust/latest/dg/credentials.html
+#[derive(Default, Clone, Debug, Serialize, Deserialize)]
+pub struct AWSDynamoDBConfig {
+  pub access_key_id: String,
+  pub secret_access_key: String,
+  // Region list: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
+  pub region: String,
+  pub enable: bool,
+}
+
+impl AWSDynamoDBConfig {
+  pub fn from_env() -> Option<Self> {
+    let access_key_id = std::env::var(AWS_ACCESS_KEY_ID).ok()?;
+    let secret_access_key = std::env::var(AWS_SECRET_ACCESS_KEY).ok()?;
+    let region = std::env::var(AWS_REGION).unwrap_or_else(|_| "us-east-1".to_string());
+    Some(Self {
+      access_key_id,
+      secret_access_key,
+      region,
+      enable: true,
+    })
+  }
+
+  pub fn write_env(&self) {
+    std::env::set_var(AWS_ACCESS_KEY_ID, &self.access_key_id);
+    std::env::set_var(AWS_SECRET_ACCESS_KEY, &self.secret_access_key);
+    std::env::set_var(AWS_REGION, &self.region);
+  }
+}

+ 26 - 0
frontend/rust-lib/collab-integrate/src/lib.rs

@@ -0,0 +1,26 @@
+pub use collab::core::collab::MutexCollab;
+pub use collab::preclude::Snapshot;
+pub use collab_persistence::doc::YrsDocAction;
+pub use collab_persistence::error::PersistenceError;
+#[cfg(any(
+  feature = "appflowy_cloud_integrate",
+  feature = "supabase_integrate",
+  feature = "rocksdb_plugin"
+))]
+pub use collab_persistence::kv::rocks_kv::RocksCollabDB;
+pub use collab_persistence::snapshot::CollabSnapshot;
+#[cfg(feature = "supabase_integrate")]
+pub use collab_plugins::cloud_storage::*;
+#[cfg(any(
+  feature = "appflowy_cloud_integrate",
+  feature = "supabase_integrate",
+  feature = "rocksdb_plugin"
+))]
+pub use collab_plugins::local_storage::CollabPersistenceConfig;
+#[cfg(feature = "snapshot_plugin")]
+pub use collab_plugins::snapshot::{
+  calculate_snapshot_diff, try_encode_snapshot, SnapshotPersistence,
+};
+
+pub mod collab_builder;
+pub mod config;

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

@@ -24,15 +24,15 @@ 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" }
-flowy-notification = { path = "../flowy-notification" }
-flowy-net = { path = "../flowy-net" }
+# workspace
+lib-dispatch = { workspace = true }
+flowy-core = { workspace = true }
+flowy-notification = { workspace = true }
+flowy-server = { workspace = true }
+flowy-server-config = { workspace = true }
+collab-integrate = { workspace = true }
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
-flowy-server = { path = "../flowy-server" }
-flowy-server-config = { path = "../flowy-server-config" }
 
 [features]
 default = ["dart", "rev-sqlite"]

+ 5 - 3
frontend/rust-lib/flowy-config/Cargo.toml

@@ -6,12 +6,14 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-flowy-sqlite = { path = "../flowy-sqlite" }
+# workspace
+flowy-sqlite = { workspace = true }
+lib-dispatch = { workspace = true }
+flowy-error = { workspace = true }
+
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
-lib-dispatch = { path = "../lib-dispatch" }
 protobuf = {version = "2.28.0"}
 bytes = { version = "1.4" }
-flowy-error = { path = "../flowy-error" }
 strum_macros = "0.21"
 
 [build-dependencies]

+ 22 - 21
frontend/rust-lib/flowy-core/Cargo.toml

@@ -6,28 +6,29 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-lib-dispatch = { path = "../lib-dispatch" }
-lib-log = { path = "../lib-log" }
-flowy-user = { path = "../flowy-user" }
-flowy-user-deps = { path = "../flowy-user-deps" }
-flowy-net = { path = "../flowy-net" }
-flowy-folder2 = { path = "../flowy-folder2" }
-flowy-folder-deps = { path = "../flowy-folder-deps" }
-flowy-database2 = { path = "../flowy-database2" }
-flowy-database-deps = { path = "../flowy-database-deps" }
-flowy-sqlite = { path = "../flowy-sqlite" }
-flowy-document2 = { path = "../flowy-document2" }
-flowy-document-deps = { path = "../flowy-document-deps" }
-flowy-error = { path = "../flowy-error" }
-flowy-task = { path = "../flowy-task" }
-flowy-server = { path = "../flowy-server" }
-flowy-server-config = { path = "../flowy-server-config" }
-flowy-config = { path = "../flowy-config" }
-appflowy-integrate = { version = "0.1.0", features = ["postgres_storage_plugin", "snapshot_plugin"] }
+lib-dispatch = { workspace = true }
+lib-log = { workspace = true }
+flowy-user = { workspace = true }
+flowy-user-deps = { workspace = true }
+flowy-folder2 = { workspace = true }
+flowy-folder-deps = { workspace = true }
+flowy-database2 = { workspace = true }
+flowy-database-deps = { workspace = true }
+flowy-sqlite = { workspace = true }
+flowy-document2 = { workspace = true }
+flowy-document-deps = { workspace = true }
+flowy-error = { workspace = true }
+flowy-task = { workspace = true }
+flowy-server = { workspace = true }
+flowy-server-config = { workspace = true }
+flowy-config = { workspace = true }
+collab-integrate = { workspace = true, features = ["supabase_integrate", "appflowy_cloud_integrate", "snapshot_plugin"] }
 collab-define = { version = "0.1.0" }
+collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
+collab = { version = "0.1.0" }
 diesel = { version = "1.4.8", features = ["sqlite"] }
 uuid = { version = "1.3.3", features = ["v4"] }
-flowy-storage = { path = "../flowy-storage" }
+flowy-storage = { workspace = true }
 
 tracing = { version = "0.1", features = ["log"] }
 futures-core = { version = "0.3", default-features = false }
@@ -35,6 +36,7 @@ bytes = "1.4"
 tokio = { version = "1.26", features = ["full"] }
 console-subscriber = { version = "0.1.8", optional = true }
 parking_lot = "0.12.1"
+anyhow = "1.0.75"
 
 lib-infra = { path = "../../../shared-lib/lib-infra" }
 serde = "1.0"
@@ -49,7 +51,6 @@ native_sync = []
 use_bunyan = ["lib-log/use_bunyan"]
 dart = [
     "flowy-user/dart",
-    "flowy-net/dart",
     "flowy-folder2/dart",
     "flowy-database2/dart",
     "flowy-document2/dart",
@@ -57,7 +58,6 @@ dart = [
 ]
 ts = [
     "flowy-user/ts",
-    "flowy-net/ts",
     "flowy-folder2/ts",
     "flowy-database2/ts",
     "flowy-document2/ts",
@@ -67,3 +67,4 @@ rev-sqlite = [
     "flowy-user/rev-sqlite",
 ]
 openssl_vendored = ["flowy-sqlite/openssl_vendored"]
+

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

@@ -1,10 +1,10 @@
 use std::sync::Weak;
 
-use appflowy_integrate::{
-  calculate_snapshot_diff, CollabSnapshot, PersistenceError, SnapshotPersistence,
-};
 use diesel::SqliteConnection;
 
+use collab_integrate::{
+  calculate_snapshot_diff, CollabSnapshot, PersistenceError, SnapshotPersistence,
+};
 use flowy_error::FlowyError;
 use flowy_sqlite::{
   insert_or_ignore_into,

+ 2 - 2
frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs

@@ -1,9 +1,9 @@
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::RocksCollabDB;
 use tokio::sync::RwLock;
 
+use collab_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab_integrate::RocksCollabDB;
 use flowy_database2::{DatabaseManager, DatabaseUser};
 use flowy_database_deps::cloud::DatabaseCloudService;
 use flowy_error::FlowyError;

+ 2 - 3
frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs

@@ -1,8 +1,7 @@
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::RocksCollabDB;
-
+use collab_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab_integrate::RocksCollabDB;
 use flowy_database2::DatabaseManager;
 use flowy_document2::manager::{DocumentManager, DocumentUser};
 use flowy_document_deps::cloud::DocumentCloudService;

+ 2 - 2
frontend/rust-lib/flowy-core/src/deps_resolve/folder_deps.rs

@@ -2,11 +2,11 @@ use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::RocksCollabDB;
 use bytes::Bytes;
 use tokio::sync::RwLock;
 
+use collab_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab_integrate::RocksCollabDB;
 use flowy_database2::entities::DatabaseLayoutPB;
 use flowy_database2::services::share::csv::CSVFormat;
 use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};

+ 1 - 0
frontend/rust-lib/flowy-core/src/integrate/mod.rs

@@ -1 +1,2 @@
 pub(crate) mod server;
+mod trait_impls;

+ 54 - 312
frontend/rust-lib/flowy-core/src/integrate/server.rs

@@ -2,18 +2,11 @@ use std::collections::HashMap;
 use std::fmt::{Display, Formatter};
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::collab_builder::{CollabStorageProvider, CollabStorageType};
-use appflowy_integrate::{RemoteCollabStorage, YrsDocAction};
-use bytes::Bytes;
-use collab_define::{CollabObject, CollabType};
 use parking_lot::RwLock;
 use serde_repr::*;
 
-use flowy_database_deps::cloud::*;
-use flowy_document2::deps::DocumentData;
-use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot};
+use collab_integrate::YrsDocAction;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
-use flowy_folder_deps::cloud::*;
 use flowy_server::af_cloud::configuration::appflowy_cloud_server_configuration;
 use flowy_server::af_cloud::AFCloudServer;
 use flowy_server::local_server::{LocalServer, LocalServerDB};
@@ -21,22 +14,19 @@ use flowy_server::supabase::SupabaseServer;
 use flowy_server::{AppFlowyEncryption, AppFlowyServer, EncryptionImpl};
 use flowy_server_config::supabase_config::SupabaseConfiguration;
 use flowy_sqlite::kv::StorePreferences;
-use flowy_storage::{FileStorageService, StorageObject};
-use flowy_user::event_map::UserCloudServiceProvider;
 use flowy_user::services::database::{
   get_user_profile, get_user_workspace, open_collab_db, open_user_db,
 };
 use flowy_user_deps::cloud::UserCloudService;
 use flowy_user_deps::entities::*;
-use lib_infra::future::FutureResult;
 
 use crate::AppFlowyCoreConfig;
 
-const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
+pub(crate) const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
 
 #[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
 #[repr(u8)]
-pub enum ServerProviderType {
+pub enum ServerType {
   /// Local server provider.
   /// Offline mode, no user authentication and the data is stored locally.
   Local = 0,
@@ -45,48 +35,48 @@ pub enum ServerProviderType {
   /// progress.
   AppFlowyCloud = 1,
   /// Supabase server provider.
-  /// It uses supabase's postgresql database to store data and user authentication.
+  /// It uses supabase postgresql database to store data and user authentication.
   Supabase = 2,
 }
 
-impl Display for ServerProviderType {
+impl Display for ServerType {
   fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
     match self {
-      ServerProviderType::Local => write!(f, "Local"),
-      ServerProviderType::AppFlowyCloud => write!(f, "AppFlowyCloud"),
-      ServerProviderType::Supabase => write!(f, "Supabase"),
+      ServerType::Local => write!(f, "Local"),
+      ServerType::AppFlowyCloud => write!(f, "AppFlowyCloud"),
+      ServerType::Supabase => write!(f, "Supabase"),
     }
   }
 }
 
-/// 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
+/// The [ServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
+/// the auth type, the [ServerProvider] will create a new [AppFlowyServer] if it doesn't
 /// exist.
 /// Each server implements the [AppFlowyServer] trait, which provides the [UserCloudService], etc.
-pub struct AppFlowyServerProvider {
+pub struct ServerProvider {
   config: AppFlowyCoreConfig,
-  provider_type: RwLock<ServerProviderType>,
-  providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
-  encryption: RwLock<Arc<dyn AppFlowyEncryption>>,
-  store_preferences: Weak<StorePreferences>,
-  cache_user_service: RwLock<HashMap<ServerProviderType, Arc<dyn UserCloudService>>>,
-
-  device_id: Arc<RwLock<String>>,
-  enable_sync: RwLock<bool>,
-  uid: Arc<RwLock<Option<i64>>>,
+  server_type: RwLock<ServerType>,
+  providers: RwLock<HashMap<ServerType, Arc<dyn AppFlowyServer>>>,
+  pub(crate) encryption: RwLock<Arc<dyn AppFlowyEncryption>>,
+  pub(crate) store_preferences: Weak<StorePreferences>,
+  pub(crate) cache_user_service: RwLock<HashMap<ServerType, Arc<dyn UserCloudService>>>,
+
+  pub(crate) device_id: Arc<RwLock<String>>,
+  pub(crate) enable_sync: RwLock<bool>,
+  pub(crate) uid: Arc<RwLock<Option<i64>>>,
 }
 
-impl AppFlowyServerProvider {
+impl ServerProvider {
   pub fn new(
     config: AppFlowyCoreConfig,
-    provider_type: ServerProviderType,
+    provider_type: ServerType,
     store_preferences: Weak<StorePreferences>,
   ) -> Self {
     let encryption = EncryptionImpl::new(None);
     Self {
       config,
-      provider_type: RwLock::new(provider_type),
-      device_id: Default::default(),
+      server_type: RwLock::new(provider_type),
+      device_id: Arc::new(RwLock::new(uuid::Uuid::new_v4().to_string())),
       providers: RwLock::new(HashMap::new()),
       enable_sync: RwLock::new(true),
       encryption: RwLock::new(Arc::new(encryption)),
@@ -96,42 +86,51 @@ impl AppFlowyServerProvider {
     }
   }
 
-  pub fn provider_type(&self) -> ServerProviderType {
-    self.provider_type.read().clone()
+  pub fn get_server_type(&self) -> ServerType {
+    self.server_type.read().clone()
+  }
+
+  pub fn set_server_type(&self, server_type: ServerType) {
+    *self.server_type.write() = server_type;
   }
 
   /// Returns a [AppFlowyServer] trait implementation base on the provider_type.
-  fn get_provider(
+  pub(crate) fn get_server(
     &self,
-    provider_type: &ServerProviderType,
+    server_type: &ServerType,
   ) -> FlowyResult<Arc<dyn AppFlowyServer>> {
-    if let Some(provider) = self.providers.read().get(provider_type) {
+    if let Some(provider) = self.providers.read().get(server_type) {
       return Ok(provider.clone());
     }
 
-    let server = match provider_type {
-      ServerProviderType::Local => {
+    let server = match server_type {
+      ServerType::Local => {
         let local_db = Arc::new(LocalServerDBImpl {
           storage_path: self.config.storage_path.clone(),
         });
         let server = Arc::new(LocalServer::new(local_db));
-
         Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
       },
-      ServerProviderType::AppFlowyCloud => {
+      ServerType::AppFlowyCloud => {
         let config = appflowy_cloud_server_configuration().map_err(|e| {
           FlowyError::new(
             ErrorCode::InvalidAuthConfig,
             format!(
               "Missing self host config: {:?}. Error: {:?}",
-              provider_type, e
+              server_type, e
             ),
           )
         })?;
-        let server = Arc::new(AFCloudServer::new(config));
+        tracing::trace!("🔑AppFlowy cloud config: {:?}", config);
+        let server = Arc::new(AFCloudServer::new(
+          config,
+          *self.enable_sync.read(),
+          self.device_id.clone(),
+        ));
+
         Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
       },
-      ServerProviderType::Supabase => {
+      ServerType::Supabase => {
         let config = match SupabaseConfiguration::from_env() {
           Ok(config) => config,
           Err(e) => {
@@ -155,287 +154,30 @@ impl AppFlowyServerProvider {
     self
       .providers
       .write()
-      .insert(provider_type.clone(), server.clone());
+      .insert(server_type.clone(), server.clone());
     Ok(server)
   }
 }
 
-impl FileStorageService for AppFlowyServerProvider {
-  fn create_object(&self, object: StorageObject) -> FutureResult<String, FlowyError> {
-    let server = self.get_provider(&self.provider_type.read());
-    FutureResult::new(async move {
-      let storage = server?.file_storage().ok_or(FlowyError::internal())?;
-      storage.create_object(object).await
-    })
-  }
-
-  fn delete_object_by_url(&self, object_url: String) -> FutureResult<(), FlowyError> {
-    let server = self.get_provider(&self.provider_type.read());
-    FutureResult::new(async move {
-      let storage = server?.file_storage().ok_or(FlowyError::internal())?;
-      storage.delete_object_by_url(object_url).await
-    })
-  }
-
-  fn get_object_by_url(&self, object_url: String) -> FutureResult<Bytes, FlowyError> {
-    let server = self.get_provider(&self.provider_type.read());
-    FutureResult::new(async move {
-      let storage = server?.file_storage().ok_or(FlowyError::internal())?;
-      storage.get_object_by_url(object_url).await
-    })
-  }
-}
-
-impl UserCloudServiceProvider for AppFlowyServerProvider {
-  fn set_enable_sync(&self, uid: i64, enable_sync: bool) {
-    match self.get_provider(&self.provider_type.read()) {
-      Ok(server) => {
-        server.set_enable_sync(uid, enable_sync);
-        *self.enable_sync.write() = enable_sync;
-        *self.uid.write() = Some(uid);
-      },
-      Err(e) => tracing::error!("🔴Failed to enable sync: {:?}", e),
-    }
-  }
-
-  fn set_encrypt_secret(&self, secret: String) {
-    tracing::info!("🔑Set encrypt secret");
-    self.encryption.write().set_secret(secret);
-  }
-
-  /// When user login, the provider type is set by the [AuthType] and save to disk for next use.
-  ///
-  /// 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();
-    *self.provider_type.write() = provider_type.clone();
-
-    match self.store_preferences.upgrade() {
-      None => tracing::error!("🔴Failed to update server provider type: store preferences is drop"),
-      Some(store_preferences) => {
-        match store_preferences.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);
-          },
-        }
-      },
-    }
-  }
-
-  fn set_device_id(&self, device_id: &str) {
-    *self.device_id.write() = device_id.to_string();
-  }
-
-  /// Returns the [UserCloudService] base on the current [ServerProviderType].
-  /// Creates a new [AppFlowyServer] if it doesn't exist.
-  fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {
-    if let Some(user_service) = self
-      .cache_user_service
-      .read()
-      .get(&self.provider_type.read())
-    {
-      return Ok(user_service.clone());
-    }
-
-    let provider_type = self.provider_type.read().clone();
-    let user_service = self.get_provider(&provider_type)?.user_service();
-    self
-      .cache_user_service
-      .write()
-      .insert(provider_type, user_service.clone());
-    Ok(user_service)
-  }
-
-  fn service_name(&self) -> String {
-    self.provider_type.read().to_string()
-  }
-}
-
-impl FolderCloudService for AppFlowyServerProvider {
-  fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, Error> {
-    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 get_folder_data(&self, workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {
-    let server = self.get_provider(&self.provider_type.read());
-    let workspace_id = workspace_id.to_string();
-    FutureResult::new(async move {
-      server?
-        .folder_service()
-        .get_folder_data(&workspace_id)
-        .await
-    })
-  }
-
-  fn get_folder_snapshots(
-    &self,
-    workspace_id: &str,
-    limit: usize,
-  ) -> FutureResult<Vec<FolderSnapshot>, Error> {
-    let workspace_id = workspace_id.to_string();
-    let server = self.get_provider(&self.provider_type.read());
-    FutureResult::new(async move {
-      server?
-        .folder_service()
-        .get_folder_snapshots(&workspace_id, limit)
-        .await
-    })
-  }
-
-  fn get_folder_updates(&self, workspace_id: &str, uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
-    let workspace_id = workspace_id.to_string();
-    let server = self.get_provider(&self.provider_type.read());
-    FutureResult::new(async move {
-      server?
-        .folder_service()
-        .get_folder_updates(&workspace_id, uid)
-        .await
-    })
-  }
-
-  fn service_name(&self) -> String {
-    self
-      .get_provider(&self.provider_type.read())
-      .map(|provider| provider.folder_service().service_name())
-      .unwrap_or_default()
-  }
-}
-
-impl DatabaseCloudService for AppFlowyServerProvider {
-  fn get_collab_update(
-    &self,
-    object_id: &str,
-    object_ty: CollabType,
-  ) -> FutureResult<CollabObjectUpdate, Error> {
-    let server = self.get_provider(&self.provider_type.read());
-    let database_id = object_id.to_string();
-    FutureResult::new(async move {
-      server?
-        .database_service()
-        .get_collab_update(&database_id, object_ty)
-        .await
-    })
-  }
-
-  fn batch_get_collab_updates(
-    &self,
-    object_ids: Vec<String>,
-    object_ty: CollabType,
-  ) -> FutureResult<CollabObjectUpdateByOid, Error> {
-    let server = self.get_provider(&self.provider_type.read());
-    FutureResult::new(async move {
-      server?
-        .database_service()
-        .batch_get_collab_updates(object_ids, object_ty)
-        .await
-    })
-  }
-
-  fn get_collab_snapshots(
-    &self,
-    object_id: &str,
-    limit: usize,
-  ) -> FutureResult<Vec<DatabaseSnapshot>, Error> {
-    let server = self.get_provider(&self.provider_type.read());
-    let database_id = object_id.to_string();
-    FutureResult::new(async move {
-      server?
-        .database_service()
-        .get_collab_snapshots(&database_id, limit)
-        .await
-    })
-  }
-}
-
-impl DocumentCloudService for AppFlowyServerProvider {
-  fn get_document_updates(&self, document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
-    let server = self.get_provider(&self.provider_type.read());
-    let document_id = document_id.to_string();
-    FutureResult::new(async move {
-      server?
-        .document_service()
-        .get_document_updates(&document_id)
-        .await
-    })
-  }
-
-  fn get_document_snapshots(
-    &self,
-    document_id: &str,
-    limit: usize,
-  ) -> FutureResult<Vec<DocumentSnapshot>, Error> {
-    let server = self.get_provider(&self.provider_type.read());
-    let document_id = document_id.to_string();
-    FutureResult::new(async move {
-      server?
-        .document_service()
-        .get_document_snapshots(&document_id, limit)
-        .await
-    })
-  }
-
-  fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, Error> {
-    let server = self.get_provider(&self.provider_type.read());
-    let document_id = document_id.to_string();
-    FutureResult::new(async move {
-      server?
-        .document_service()
-        .get_document_data(&document_id)
-        .await
-    })
-  }
-}
-
-impl CollabStorageProvider for AppFlowyServerProvider {
-  fn storage_type(&self) -> CollabStorageType {
-    self.provider_type().into()
-  }
-
-  fn get_storage(
-    &self,
-    collab_object: &CollabObject,
-    storage_type: &CollabStorageType,
-  ) -> Option<Arc<dyn RemoteCollabStorage>> {
-    match storage_type {
-      CollabStorageType::Local => None,
-      CollabStorageType::AWS => None,
-      CollabStorageType::Supabase => self
-        .get_provider(&ServerProviderType::Supabase)
-        .ok()
-        .and_then(|provider| provider.collab_storage(collab_object)),
-    }
-  }
-
-  fn is_sync_enabled(&self) -> bool {
-    *self.enable_sync.read()
-  }
-}
-
-impl From<AuthType> for ServerProviderType {
+impl From<AuthType> for ServerType {
   fn from(auth_provider: AuthType) -> Self {
     match auth_provider {
-      AuthType::Local => ServerProviderType::Local,
-      AuthType::SelfHosted => ServerProviderType::AppFlowyCloud,
-      AuthType::Supabase => ServerProviderType::Supabase,
+      AuthType::Local => ServerType::Local,
+      AuthType::SelfHosted => ServerType::AppFlowyCloud,
+      AuthType::Supabase => ServerType::Supabase,
     }
   }
 }
 
-impl From<&AuthType> for ServerProviderType {
+impl From<&AuthType> for ServerType {
   fn from(auth_provider: &AuthType) -> Self {
     Self::from(auth_provider.clone())
   }
 }
 
-pub fn current_server_provider(store_preferences: &Arc<StorePreferences>) -> ServerProviderType {
-  match store_preferences.get_object::<ServerProviderType>(SERVER_PROVIDER_TYPE_KEY) {
-    None => ServerProviderType::Local,
+pub fn current_server_provider(store_preferences: &Arc<StorePreferences>) -> ServerType {
+  match store_preferences.get_object::<ServerType>(SERVER_PROVIDER_TYPE_KEY) {
+    None => ServerType::Local,
     Some(provider_type) => provider_type,
   }
 }

+ 321 - 0
frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs

@@ -0,0 +1,321 @@
+use std::sync::Arc;
+
+use anyhow::Error;
+use bytes::Bytes;
+use collab::core::origin::{CollabClient, CollabOrigin};
+use collab::preclude::CollabPlugin;
+use collab_define::CollabType;
+use collab_plugins::sync_plugin::{SyncObject, SyncPlugin};
+
+use collab_integrate::collab_builder::{CollabPluginContext, CollabSource, CollabStorageProvider};
+use collab_integrate::postgres::SupabaseDBPlugin;
+use flowy_database_deps::cloud::{
+  CollabObjectUpdate, CollabObjectUpdateByOid, DatabaseCloudService, DatabaseSnapshot,
+};
+use flowy_document2::deps::DocumentData;
+use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot};
+use flowy_error::FlowyError;
+use flowy_folder_deps::cloud::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
+use flowy_storage::{FileStorageService, StorageObject};
+use flowy_user::event_map::UserCloudServiceProvider;
+use flowy_user_deps::cloud::UserCloudService;
+use flowy_user_deps::entities::AuthType;
+use lib_infra::async_trait::async_trait;
+use lib_infra::future::FutureResult;
+
+use crate::integrate::server::{ServerProvider, ServerType, SERVER_PROVIDER_TYPE_KEY};
+
+impl FileStorageService for ServerProvider {
+  fn create_object(&self, object: StorageObject) -> FutureResult<String, FlowyError> {
+    let server = self.get_server(&self.get_server_type());
+    FutureResult::new(async move {
+      let storage = server?.file_storage().ok_or(FlowyError::internal())?;
+      storage.create_object(object).await
+    })
+  }
+
+  fn delete_object_by_url(&self, object_url: String) -> FutureResult<(), FlowyError> {
+    let server = self.get_server(&self.get_server_type());
+    FutureResult::new(async move {
+      let storage = server?.file_storage().ok_or(FlowyError::internal())?;
+      storage.delete_object_by_url(object_url).await
+    })
+  }
+
+  fn get_object_by_url(&self, object_url: String) -> FutureResult<Bytes, FlowyError> {
+    let server = self.get_server(&self.get_server_type());
+    FutureResult::new(async move {
+      let storage = server?.file_storage().ok_or(FlowyError::internal())?;
+      storage.get_object_by_url(object_url).await
+    })
+  }
+}
+
+impl UserCloudServiceProvider for ServerProvider {
+  fn set_enable_sync(&self, uid: i64, enable_sync: bool) {
+    match self.get_server(&self.get_server_type()) {
+      Ok(server) => {
+        server.set_enable_sync(uid, enable_sync);
+        *self.enable_sync.write() = enable_sync;
+        *self.uid.write() = Some(uid);
+      },
+      Err(e) => tracing::error!("🔴Failed to enable sync: {:?}", e),
+    }
+  }
+
+  fn set_encrypt_secret(&self, secret: String) {
+    tracing::info!("🔑Set encrypt secret");
+    self.encryption.write().set_secret(secret);
+  }
+
+  /// When user login, the provider type is set by the [AuthType] and save to disk for next use.
+  ///
+  /// Each [AuthType] has a corresponding [ServerType]. The [ServerType] is used
+  /// to create a new [AppFlowyServer] if it doesn't exist. Once the [ServerType] is set,
+  /// it will be used when user open the app again.
+  ///
+  fn set_auth_type(&self, auth_type: AuthType) {
+    let server_type: ServerType = auth_type.into();
+    self.set_server_type(server_type.clone());
+
+    match self.store_preferences.upgrade() {
+      None => tracing::error!("🔴Failed to update server provider type: store preferences is drop"),
+      Some(store_preferences) => {
+        match store_preferences.set_object(SERVER_PROVIDER_TYPE_KEY, server_type.clone()) {
+          Ok(_) => tracing::trace!("Update server provider type to: {:?}", server_type),
+          Err(e) => {
+            tracing::error!("🔴Failed to update server provider type: {:?}", e);
+          },
+        }
+      },
+    }
+  }
+
+  fn set_device_id(&self, device_id: &str) {
+    if device_id.is_empty() {
+      tracing::error!("🔴Device id is empty");
+      return;
+    }
+
+    *self.device_id.write() = device_id.to_string();
+  }
+
+  /// Returns the [UserCloudService] base on the current [ServerType].
+  /// Creates a new [AppFlowyServer] if it doesn't exist.
+  fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {
+    if let Some(user_service) = self.cache_user_service.read().get(&self.get_server_type()) {
+      return Ok(user_service.clone());
+    }
+
+    let server_type = self.get_server_type();
+    let user_service = self.get_server(&server_type)?.user_service();
+    self
+      .cache_user_service
+      .write()
+      .insert(server_type, user_service.clone());
+    Ok(user_service)
+  }
+
+  fn service_name(&self) -> String {
+    self.get_server_type().to_string()
+  }
+}
+
+impl FolderCloudService for ServerProvider {
+  fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, Error> {
+    let server = self.get_server(&self.get_server_type());
+    let name = name.to_string();
+    FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
+  }
+
+  fn get_folder_data(&self, workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {
+    let server = self.get_server(&self.get_server_type());
+    let workspace_id = workspace_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .folder_service()
+        .get_folder_data(&workspace_id)
+        .await
+    })
+  }
+
+  fn get_folder_snapshots(
+    &self,
+    workspace_id: &str,
+    limit: usize,
+  ) -> FutureResult<Vec<FolderSnapshot>, Error> {
+    let workspace_id = workspace_id.to_string();
+    let server = self.get_server(&self.get_server_type());
+    FutureResult::new(async move {
+      server?
+        .folder_service()
+        .get_folder_snapshots(&workspace_id, limit)
+        .await
+    })
+  }
+
+  fn get_folder_updates(&self, workspace_id: &str, uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
+    let workspace_id = workspace_id.to_string();
+    let server = self.get_server(&self.get_server_type());
+    FutureResult::new(async move {
+      server?
+        .folder_service()
+        .get_folder_updates(&workspace_id, uid)
+        .await
+    })
+  }
+
+  fn service_name(&self) -> String {
+    self
+      .get_server(&self.get_server_type())
+      .map(|provider| provider.folder_service().service_name())
+      .unwrap_or_default()
+  }
+}
+
+impl DatabaseCloudService for ServerProvider {
+  fn get_collab_update(
+    &self,
+    object_id: &str,
+    object_ty: CollabType,
+  ) -> FutureResult<CollabObjectUpdate, Error> {
+    let server = self.get_server(&self.get_server_type());
+    let database_id = object_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .database_service()
+        .get_collab_update(&database_id, object_ty)
+        .await
+    })
+  }
+
+  fn batch_get_collab_updates(
+    &self,
+    object_ids: Vec<String>,
+    object_ty: CollabType,
+  ) -> FutureResult<CollabObjectUpdateByOid, Error> {
+    let server = self.get_server(&self.get_server_type());
+    FutureResult::new(async move {
+      server?
+        .database_service()
+        .batch_get_collab_updates(object_ids, object_ty)
+        .await
+    })
+  }
+
+  fn get_collab_snapshots(
+    &self,
+    object_id: &str,
+    limit: usize,
+  ) -> FutureResult<Vec<DatabaseSnapshot>, Error> {
+    let server = self.get_server(&self.get_server_type());
+    let database_id = object_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .database_service()
+        .get_collab_snapshots(&database_id, limit)
+        .await
+    })
+  }
+}
+
+impl DocumentCloudService for ServerProvider {
+  fn get_document_updates(&self, document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
+    let server = self.get_server(&self.get_server_type());
+    let document_id = document_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .document_service()
+        .get_document_updates(&document_id)
+        .await
+    })
+  }
+
+  fn get_document_snapshots(
+    &self,
+    document_id: &str,
+    limit: usize,
+  ) -> FutureResult<Vec<DocumentSnapshot>, Error> {
+    let server = self.get_server(&self.get_server_type());
+    let document_id = document_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .document_service()
+        .get_document_snapshots(&document_id, limit)
+        .await
+    })
+  }
+
+  fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, Error> {
+    let server = self.get_server(&self.get_server_type());
+    let document_id = document_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .document_service()
+        .get_document_data(&document_id)
+        .await
+    })
+  }
+}
+
+#[async_trait]
+impl CollabStorageProvider for ServerProvider {
+  fn storage_source(&self) -> CollabSource {
+    self.get_server_type().into()
+  }
+
+  async fn get_plugins(&self, context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
+    let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![];
+    match context {
+      CollabPluginContext::Local => {},
+      CollabPluginContext::AppFlowyCloud {
+        uid: _,
+        collab_object,
+        local_collab,
+      } => {
+        if let Ok(server) = self.get_server(&ServerType::AppFlowyCloud) {
+          match server.collab_ws_channel(&collab_object.object_id).await {
+            Ok(Some(channel)) => {
+              let origin = CollabOrigin::Client(CollabClient::new(
+                collab_object.uid,
+                collab_object.device_id.clone(),
+              ));
+              let sync_object = SyncObject::from(collab_object);
+              let (sink, stream) = (channel.sink(), channel.stream());
+              let sync_plugin = SyncPlugin::new(origin, sync_object, local_collab, sink, stream);
+              plugins.push(Arc::new(sync_plugin));
+            },
+            Ok(None) => {},
+            Err(err) => tracing::error!("🔴Failed to get collab ws channel: {:?}", err),
+          }
+        }
+      },
+      CollabPluginContext::Supabase {
+        uid,
+        collab_object,
+        local_collab,
+        local_collab_db,
+      } => {
+        if let Some(remote_collab_storage) = self
+          .get_server(&ServerType::Supabase)
+          .ok()
+          .and_then(|provider| provider.collab_storage(&collab_object))
+        {
+          plugins.push(Arc::new(SupabaseDBPlugin::new(
+            uid,
+            collab_object,
+            local_collab,
+            1,
+            remote_collab_storage,
+            local_collab_db,
+          )));
+        }
+      },
+    }
+    plugins
+  }
+
+  fn is_sync_enabled(&self) -> bool {
+    *self.enable_sync.read()
+  }
+}

+ 14 - 16
frontend/rust-lib/flowy-core/src/lib.rs

@@ -10,9 +10,9 @@ use std::{
   },
 };
 
-use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, CollabStorageType};
 use tokio::sync::RwLock;
 
+use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabSource};
 use flowy_database2::DatabaseManager;
 use flowy_document2::manager::DocumentManager;
 use flowy_error::FlowyResult;
@@ -31,9 +31,7 @@ use module::make_plugins;
 pub use module::*;
 
 use crate::deps_resolve::*;
-use crate::integrate::server::{
-  current_server_provider, AppFlowyServerProvider, ServerProviderType,
-};
+use crate::integrate::server::{current_server_provider, ServerProvider, ServerType};
 
 mod deps_resolve;
 mod integrate;
@@ -121,7 +119,7 @@ pub struct AppFlowyCore {
   pub folder_manager: Arc<FolderManager>,
   pub database_manager: Arc<DatabaseManager>,
   pub event_dispatcher: Arc<AFPluginDispatcher>,
-  pub server_provider: Arc<AppFlowyServerProvider>,
+  pub server_provider: Arc<ServerProvider>,
   pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
   pub store_preference: Arc<StorePreferences>,
 }
@@ -147,7 +145,7 @@ impl AppFlowyCore {
     runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
 
     let provider_type = current_server_provider(&store_preference);
-    let server_provider = Arc::new(AppFlowyServerProvider::new(
+    let server_provider = Arc::new(ServerProvider::new(
       config.clone(),
       provider_type,
       Arc::downgrade(&store_preference),
@@ -282,7 +280,7 @@ struct UserStatusCallbackImpl {
   folder_manager: Arc<FolderManager>,
   database_manager: Arc<DatabaseManager>,
   document_manager: Arc<DocumentManager>,
-  server_provider: Arc<AppFlowyServerProvider>,
+  server_provider: Arc<ServerProvider>,
   #[allow(dead_code)]
   config: AppFlowyCoreConfig,
 }
@@ -298,7 +296,8 @@ impl UserStatusCallback for UserStatusCallbackImpl {
     _device_id: &str,
   ) -> Fut<FlowyResult<()>> {
     let user_workspace = user_workspace.clone();
-    let collab_builder = self.collab_builder.clone();
+    self.collab_builder.initialize(user_workspace.id.clone());
+
     let folder_manager = self.folder_manager.clone();
     let database_manager = self.database_manager.clone();
     let document_manager = self.document_manager.clone();
@@ -315,7 +314,6 @@ impl UserStatusCallback for UserStatusCallbackImpl {
     }
 
     to_fut(async move {
-      collab_builder.initialize(user_workspace.id.clone());
       folder_manager
         .initialize(
           user_id,
@@ -419,13 +417,13 @@ impl UserStatusCallback for UserStatusCallbackImpl {
 
   fn open_workspace(&self, user_id: i64, user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>> {
     let user_workspace = user_workspace.clone();
-    let collab_builder = self.collab_builder.clone();
+    self.collab_builder.initialize(user_workspace.id.clone());
+
     let folder_manager = self.folder_manager.clone();
     let database_manager = self.database_manager.clone();
     let document_manager = self.document_manager.clone();
 
     to_fut(async move {
-      collab_builder.initialize(user_workspace.id.clone());
       folder_manager
         .initialize_with_workspace_id(user_id, &user_workspace.id)
         .await?;
@@ -449,12 +447,12 @@ impl UserStatusCallback for UserStatusCallbackImpl {
   }
 }
 
-impl From<ServerProviderType> for CollabStorageType {
-  fn from(server_provider: ServerProviderType) -> Self {
+impl From<ServerType> for CollabSource {
+  fn from(server_provider: ServerType) -> Self {
     match server_provider {
-      ServerProviderType::Local => CollabStorageType::Local,
-      ServerProviderType::AppFlowyCloud => CollabStorageType::Local,
-      ServerProviderType::Supabase => CollabStorageType::Supabase,
+      ServerType::Local => CollabSource::Local,
+      ServerType::AppFlowyCloud => CollabSource::Local,
+      ServerType::Supabase => CollabSource::Supabase,
     }
   }
 }

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

@@ -18,14 +18,12 @@ pub fn make_plugins(
     .unwrap();
   let user_plugin = flowy_user::event_map::init(user_session);
   let folder_plugin = flowy_folder2::event_map::init(folder_manager);
-  let network_plugin = flowy_net::event_map::init();
   let database_plugin = flowy_database2::event_map::init(database_manager);
   let document_plugin2 = flowy_document2::event_map::init(document_manager2);
   let config_plugin = flowy_config::event_map::init(store_preferences);
   vec![
     user_plugin,
     folder_plugin,
-    network_plugin,
     database_plugin,
     document_plugin2,
     config_plugin,

+ 1 - 1
frontend/rust-lib/flowy-database-deps/Cargo.toml

@@ -7,6 +7,6 @@ edition = "2021"
 
 [dependencies]
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-flowy-error = { path = "../flowy-error" }
+flowy-error = { workspace = true }
 collab-define = { version = "0.1.0" }
 anyhow = "1.0.71"

+ 7 - 7
frontend/rust-lib/flowy-database2/Cargo.toml

@@ -9,24 +9,24 @@ edition = "2021"
 collab = { version = "0.1.0" }
 collab-database = { version = "0.1.0" }
 collab-define = { version = "0.1.0" }
-appflowy-integrate = {version = "0.1.0" }
-flowy-database-deps = { path = "../flowy-database-deps" }
+collab-integrate = { workspace = true }
+flowy-database-deps = { workspace = true }
 
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
-flowy-notification = { path = "../flowy-notification" }
+flowy-notification  = { workspace = true }
 parking_lot = "0.12.1"
 protobuf = {version = "2.28.0"}
-flowy-error = { path = "../flowy-error", features = ["impl_from_dispatch_error", "impl_from_collab"]}
-lib-dispatch = { path = "../lib-dispatch" }
+flowy-error = { workspace = true, features = ["impl_from_dispatch_error", "impl_from_collab"]}
+lib-dispatch = { workspace = true }
 tokio = { version = "1.26", features = ["sync"] }
-flowy-task= { path = "../flowy-task" }
+flowy-task= { workspace = true }
 bytes = { version = "1.4" }
 tracing = { version = "0.1", features = ["log"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
 serde_repr = "0.1"
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
+chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
 rust_decimal = "1.28.1"
 rusty-money = {version = "0.4.1", features = ["iso"]}
 lazy_static = "1.4.0"

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

@@ -1,8 +1,6 @@
 use std::collections::HashMap;
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
 use collab::core::collab::{CollabRawData, MutexCollab};
 use collab_database::blocks::BlockEvent;
 use collab_database::database::{DatabaseData, YrsDocAction};
@@ -15,6 +13,8 @@ use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLay
 use collab_define::CollabType;
 use tokio::sync::RwLock;
 
+use collab_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab_integrate::{CollabPersistenceConfig, RocksCollabDB};
 use flowy_database_deps::cloud::DatabaseCloudService;
 use flowy_error::{internal_error, FlowyError, FlowyResult};
 use flowy_task::TaskDispatcher;

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs

@@ -1464,7 +1464,7 @@ impl DatabaseViewData for DatabaseViewDataImpl {
     field_id: &str,
     visibility: Option<FieldVisibility>,
   ) {
-    let field_settings_map = self.get_field_settings(view_id, &vec![field_id.to_string()]);
+    let field_settings_map = self.get_field_settings(view_id, &[field_id.to_string()]);
 
     let new_field_settings = if let Some(field_settings) = field_settings_map.get(field_id) {
       let mut field_settings = field_settings.to_owned();

+ 1 - 4
frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs

@@ -910,10 +910,7 @@ impl DatabaseViewEditor {
       .send();
   }
 
-  pub async fn v_get_field_settings(
-    &self,
-    field_ids: &Vec<String>,
-  ) -> HashMap<String, FieldSettings> {
+  pub async fn v_get_field_settings(&self, field_ids: &[String]) -> HashMap<String, FieldSettings> {
     self.delegate.get_field_settings(&self.view_id, field_ids)
   }
 

+ 3 - 2
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs

@@ -292,7 +292,7 @@ mod tests {
     let native_timestamp = 1647251762;
     let native = NaiveDateTime::from_timestamp_opt(native_timestamp, 0).unwrap();
 
-    let utc = chrono::DateTime::<chrono::Utc>::from_utc(native, chrono::Utc);
+    let utc = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(native, chrono::Utc);
     // utc_timestamp doesn't  carry timezone
     let utc_timestamp = utc.timestamp();
     assert_eq!(native_timestamp, utc_timestamp);
@@ -304,7 +304,8 @@ mod tests {
 
     // Mon Mar 14 2022 17:56:02 GMT+0800 (China Standard Time)
     let gmt_8_offset = FixedOffset::east_opt(8 * 3600).unwrap();
-    let china_local = chrono::DateTime::<chrono::Local>::from_utc(native, gmt_8_offset);
+    let china_local =
+      chrono::DateTime::<chrono::Local>::from_naive_utc_and_offset(native, gmt_8_offset);
     let china_local_time = format!(
       "{}",
       china_local.format_with_items(StrftimeItems::new(&format))

+ 2 - 12
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs

@@ -22,23 +22,13 @@ use crate::services::sort::SortCondition;
 /// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime].
 /// So, storing the field type is necessary to distinguish the field type.
 /// Most of the cases, each [FieldType] has its own [TypeOption] implementation.
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize, Default)]
 pub struct DateTypeOption {
   pub date_format: DateFormat,
   pub time_format: TimeFormat,
   pub timezone_id: String,
 }
 
-impl Default for DateTypeOption {
-  fn default() -> Self {
-    Self {
-      date_format: Default::default(),
-      time_format: Default::default(),
-      timezone_id: Default::default(),
-    }
-  }
-}
-
 impl TypeOption for DateTypeOption {
   type CellData = DateCellData;
   type CellChangeset = DateCellChangeset;
@@ -113,7 +103,7 @@ impl DateTypeOption {
     if let Some(timestamp) = timestamp {
       let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap();
       let offset = self.get_timezone_offset(naive);
-      let date_time = DateTime::<Local>::from_utc(naive, offset);
+      let date_time = DateTime::<Local>::from_naive_utc_and_offset(naive, offset);
 
       let fmt = self.date_format.format_str();
       let date = format!("{}", date_time.format(fmt));

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

@@ -108,7 +108,7 @@ impl TimestampTypeOption {
     if let Some(timestamp) = timestamp {
       let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap();
       let offset = Local::now().offset().fix();
-      let date_time = DateTime::<Local>::from_utc(naive, offset);
+      let date_time = DateTime::<Local>::from_naive_utc_and_offset(naive, offset);
 
       let fmt = self.date_format.format_str();
       let date = format!("{}", date_time.format(fmt));

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

@@ -262,16 +262,12 @@ pub fn default_type_option_data_from_type(field_type: &FieldType) -> TypeOptionD
   match field_type {
     FieldType::RichText => RichTextTypeOption::default().into(),
     FieldType::Number => NumberTypeOption::default().into(),
-    FieldType::DateTime => DateTypeOption {
-      ..Default::default()
-    }
-    .into(),
+    FieldType::DateTime => DateTypeOption::default().into(),
     FieldType::LastEditedTime | FieldType::CreatedTime => TimestampTypeOption {
       field_type: field_type.clone(),
       date_format: DateFormat::Friendly,
       time_format: TimeFormat::TwelveHour,
       include_time: true,
-      ..Default::default()
     }
     .into(),
     FieldType::SingleSelect => SingleSelectTypeOption::default().into(),

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/group/controller_impls/date_controller.rs

@@ -443,7 +443,7 @@ fn date_time_from_timestamp(timestamp: Option<i64>, timezone_id: &str) -> DateTi
         Err(_) => *Local::now().offset(),
       };
 
-      DateTime::<Local>::from_utc(naive, offset)
+      DateTime::<Local>::from_naive_utc_and_offset(naive, offset)
     },
     None => DateTime::default(),
   }

+ 1 - 1
frontend/rust-lib/flowy-document-deps/Cargo.toml

@@ -7,6 +7,6 @@ edition = "2021"
 
 [dependencies]
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-flowy-error = { path = "../flowy-error" }
+flowy-error  = { workspace = true }
 collab-document = { version = "0.1.0" }
 anyhow = "1.0.71"

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

@@ -9,14 +9,14 @@ edition = "2021"
 collab = { version = "0.1.0" }
 collab-document = { version = "0.1.0" }
 collab-define = { version = "0.1.0" }
-appflowy-integrate = {version = "0.1.0" }
-flowy-document-deps = { path = "../flowy-document-deps" }
-flowy-storage = { path = "../flowy-storage" }
+collab-integrate = { workspace = true }
+flowy-document-deps = { workspace = true }
+flowy-storage = { workspace = true }
 
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
-flowy-notification = { path = "../flowy-notification" }
+flowy-notification  = { workspace = true }
 flowy-error = { path = "../flowy-error", features = ["impl_from_serde", "impl_from_sqlite", "impl_from_dispatch_error", "impl_from_collab"] }
-lib-dispatch = { path = "../lib-dispatch" }
+lib-dispatch = { workspace = true }
 lib-infra = { path = "../../../shared-lib/lib-infra" }
 
 protobuf = {version = "2.28.0"}

+ 2 - 2
frontend/rust-lib/flowy-document2/src/manager.rs

@@ -1,8 +1,6 @@
 use std::sync::Weak;
 use std::{collections::HashMap, sync::Arc};
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::RocksCollabDB;
 use collab::core::collab::MutexCollab;
 use collab_define::CollabType;
 use collab_document::blocks::DocumentData;
@@ -11,6 +9,8 @@ use collab_document::document_data::default_document_data;
 use collab_document::YrsDocAction;
 use parking_lot::RwLock;
 
+use collab_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab_integrate::RocksCollabDB;
 use flowy_document_deps::cloud::DocumentCloudService;
 use flowy_error::{internal_error, FlowyError, FlowyResult};
 use flowy_storage::FileStorageService;

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

@@ -2,8 +2,6 @@ use std::ops::Deref;
 use std::sync::Arc;
 
 use anyhow::Error;
-use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, DefaultCollabStorageProvider};
-use appflowy_integrate::RocksCollabDB;
 use bytes::Bytes;
 use collab_document::blocks::DocumentData;
 use collab_document::document_data::default_document_data;
@@ -12,6 +10,8 @@ use parking_lot::Once;
 use tempfile::TempDir;
 use tracing_subscriber::{fmt::Subscriber, util::SubscriberInitExt, EnvFilter};
 
+use collab_integrate::collab_builder::{AppFlowyCollabBuilder, DefaultCollabStorageProvider};
+use collab_integrate::RocksCollabDB;
 use flowy_document2::document::MutexDocument;
 use flowy_document2::manager::{DocumentManager, DocumentUser};
 use flowy_document_deps::cloud::*;
@@ -57,18 +57,15 @@ impl FakeUser {
 }
 
 impl DocumentUser for FakeUser {
-  fn user_id(&self) -> Result<i64, flowy_error::FlowyError> {
+  fn user_id(&self) -> Result<i64, FlowyError> {
     Ok(1)
   }
 
-  fn token(&self) -> Result<Option<String>, flowy_error::FlowyError> {
+  fn token(&self) -> Result<Option<String>, FlowyError> {
     Ok(None)
   }
 
-  fn collab_db(
-    &self,
-    _uid: i64,
-  ) -> Result<std::sync::Weak<RocksCollabDB>, flowy_error::FlowyError> {
+  fn collab_db(&self, _uid: i64) -> Result<std::sync::Weak<RocksCollabDB>, FlowyError> {
     Ok(Arc::downgrade(&self.collab_db))
   }
 }
@@ -92,6 +89,7 @@ pub fn db() -> Arc<RocksCollabDB> {
 pub fn default_collab_builder() -> Arc<AppFlowyCollabBuilder> {
   let builder = AppFlowyCollabBuilder::new(DefaultCollabStorageProvider());
   builder.set_sync_device(uuid::Uuid::new_v4().to_string());
+  builder.initialize(uuid::Uuid::new_v4().to_string());
   Arc::new(builder)
 }
 

+ 4 - 2
frontend/rust-lib/flowy-error/Cargo.toml

@@ -12,18 +12,19 @@ bytes = "1.4"
 anyhow = "1.0"
 thiserror = "1.0"
 
-lib-dispatch = { path = "../lib-dispatch", optional = true }
+lib-dispatch = { workspace = true, optional = true }
 serde_json = {version = "1.0", optional = true}
 serde_repr = { version = "0.1" }
 serde = "1.0"
 reqwest = { version = "0.11.14", optional = true, features = ["native-tls-vendored"] }
-flowy-sqlite = { path = "../flowy-sqlite", optional = true}
+flowy-sqlite = { workspace = true, optional = true}
 r2d2 = { version = "0.8", optional = true}
 url = { version = "2.2", optional = true }
 collab-database = { version = "0.1.0", optional = true }
 collab-document = { version = "0.1.0", optional = true }
 tokio-postgres = { version = "0.7.8", optional = true }
 tokio = { version = "1.0", optional = true }
+client-api = { version = "0.1.0", optional = true }
 
 [features]
 impl_from_dispatch_error = ["lib-dispatch"]
@@ -34,6 +35,7 @@ impl_from_collab = ["collab-database", "collab-document", "impl_from_reqwest"]
 impl_from_postgres = ["tokio-postgres"]
 impl_from_tokio= ["tokio"]
 impl_from_url= ["url"]
+impl_from_appflowy_cloud = ["client-api"]
 dart = ["flowy-codegen/dart"]
 ts = ["flowy-codegen/ts"]
 

+ 11 - 1
frontend/rust-lib/flowy-error/src/code.rs

@@ -238,8 +238,18 @@ pub enum ErrorCode {
 
   #[error("Parse url failed")]
   InvalidURL = 78,
+
+  #[error("Require Email Confirmation, Sign in after email confirmation")]
+  AwaitingEmailConfirmation = 79,
+
   #[error("Text id is empty")]
-  TextIdIsEmpty = 79,
+  TextIdIsEmpty = 80,
+
+  #[error("Record already exists")]
+  RecordAlreadyExists = 81,
+
+  #[error("Missing payload")]
+  MissingPayload = 82,
 }
 
 impl ErrorCode {

+ 27 - 0
frontend/rust-lib/flowy-error/src/impl_from/cloud.rs

@@ -0,0 +1,27 @@
+use client_api::error::AppError;
+
+use crate::{ErrorCode, FlowyError};
+
+impl From<AppError> for FlowyError {
+  fn from(error: AppError) -> Self {
+    let code = match error.code {
+      client_api::error::ErrorCode::Ok => ErrorCode::Internal,
+      client_api::error::ErrorCode::Unhandled => ErrorCode::Internal,
+      client_api::error::ErrorCode::RecordNotFound => ErrorCode::RecordNotFound,
+      client_api::error::ErrorCode::RecordAlreadyExists => ErrorCode::RecordAlreadyExists,
+      client_api::error::ErrorCode::InvalidEmail => ErrorCode::EmailFormatInvalid,
+      client_api::error::ErrorCode::InvalidPassword => ErrorCode::PasswordFormatInvalid,
+      client_api::error::ErrorCode::OAuthError => ErrorCode::UserUnauthorized,
+      client_api::error::ErrorCode::MissingPayload => ErrorCode::MissingPayload,
+      client_api::error::ErrorCode::StorageError => ErrorCode::Internal,
+      client_api::error::ErrorCode::OpenError => ErrorCode::Internal,
+      client_api::error::ErrorCode::InvalidUrl => ErrorCode::InvalidURL,
+      client_api::error::ErrorCode::InvalidRequestParams => ErrorCode::InvalidParams,
+      client_api::error::ErrorCode::UrlMissingParameter => ErrorCode::InvalidParams,
+      client_api::error::ErrorCode::InvalidOAuthProvider => ErrorCode::InvalidAuthConfig,
+      client_api::error::ErrorCode::NotLoggedIn => ErrorCode::UserUnauthorized,
+    };
+
+    FlowyError::new(code, error.message)
+  }
+}

+ 2 - 0
frontend/rust-lib/flowy-error/src/impl_from/mod.rs

@@ -22,5 +22,7 @@ mod postgres;
 #[cfg(feature = "impl_from_tokio")]
 mod tokio;
 
+#[cfg(feature = "impl_from_appflowy_cloud")]
+mod cloud;
 #[cfg(feature = "impl_from_url")]
 mod url;

+ 1 - 1
frontend/rust-lib/flowy-folder-deps/Cargo.toml

@@ -7,7 +7,7 @@ edition = "2021"
 
 [dependencies]
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-flowy-error = { path = "../flowy-error" }
+flowy-error  = { workspace = true }
 collab-folder = { version = "0.1.0" }
 uuid = { version = "1.3.3", features = ["v4"] }
 anyhow = "1.0.71"

+ 6 - 6
frontend/rust-lib/flowy-folder2/Cargo.toml

@@ -9,29 +9,29 @@ edition = "2021"
 collab = { version = "0.1.0" }
 collab-folder = { version = "0.1.0" }
 collab-define = { version = "0.1.0" }
-appflowy-integrate = {version = "0.1.0" }
-flowy-folder-deps = { path = "../flowy-folder-deps" }
+collab-integrate = { workspace = true }
+flowy-folder-deps = { workspace = true }
 
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
-flowy-notification = { path = "../flowy-notification" }
+flowy-notification  = { workspace = true }
 parking_lot = "0.12.1"
 unicode-segmentation = "1.10"
 tracing = { version = "0.1", features = ["log"] }
 flowy-error = { path = "../flowy-error", features = ["impl_from_dispatch_error"]}
-lib-dispatch = { path = "../lib-dispatch" }
+lib-dispatch = { workspace = true }
 bytes = { version = "1.4" }
 lib-infra = { path = "../../../shared-lib/lib-infra" }
 tokio = { version = "1.26", features = ["full"] }
 nanoid = "0.4.0"
 lazy_static = "1.4.0"
-chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
+chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
 strum_macros = "0.21"
 protobuf = {version = "2.28.0"}
 uuid = { version = "1.3.3", features = ["v4"] }
 tokio-stream = { version = "0.1.14", features = ["sync"] }
 
 [dev-dependencies]
-flowy-folder2 = { path = "../flowy-folder2"}
+flowy-folder2  = { workspace = true }
 flowy-test = { path = "../flowy-test", default-features = false }
 
 [build-dependencies]

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

@@ -2,8 +2,6 @@ use std::collections::HashSet;
 use std::ops::Deref;
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB, YrsDocAction};
 use collab::core::collab::{CollabRawData, MutexCollab};
 use collab::core::collab_state::SyncState;
 use collab_define::CollabType;
@@ -16,6 +14,8 @@ use tokio_stream::wrappers::WatchStream;
 use tokio_stream::StreamExt;
 use tracing::{event, Level};
 
+use collab_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab_integrate::{CollabPersistenceConfig, RocksCollabDB, YrsDocAction};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_folder_deps::cloud::{gen_view_id, FolderCloudService};
 

+ 0 - 25
frontend/rust-lib/flowy-net/Cargo.toml

@@ -1,25 +0,0 @@
-[package]
-name = "flowy-net"
-version = "0.1.0"
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-lib-dispatch = { path = "../lib-dispatch" }
-protobuf = {version = "2.28.0"}
-bytes = { version = "1.4" }
-tracing = { version = "0.1"}
-
-[features]
-http_server = []
-dart = [
-    "flowy-codegen/dart",
-]
-
-ts = [
-    "flowy-codegen/ts",
-]
-
-[build-dependencies]
-flowy-codegen = { path = "../../../shared-lib/flowy-codegen"}

+ 0 - 3
frontend/rust-lib/flowy-net/Flowy.toml

@@ -1,3 +0,0 @@
-# Check out the FlowyConfig (located in flowy_toml.rs) for more details.
-proto_input = ["src/event_map.rs", "src/entities"]
-event_files = ["src/event_map.rs"]

+ 0 - 10
frontend/rust-lib/flowy-net/build.rs

@@ -1,10 +0,0 @@
-fn main() {
-  // let crate_name = env!("CARGO_PKG_NAME");
-  // flowy_codegen::protobuf_file::gen(crate_name);
-  //
-  // #[cfg(feature = "dart")]
-  // flowy_codegen::dart_event::gen(crate_name);
-  //
-  // #[cfg(feature = "ts")]
-  // flowy_codegen::ts_event::gen(crate_name);
-}

+ 0 - 2
frontend/rust-lib/flowy-net/src/entities/mod.rs

@@ -1,2 +0,0 @@
-mod network_state;
-pub use network_state::*;

+ 0 - 29
frontend/rust-lib/flowy-net/src/entities/network_state.rs

@@ -1,29 +0,0 @@
-// use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-//
-// #[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq, Default)]
-// pub enum NetworkTypePB {
-//   #[default]
-//   Unknown = 0,
-//   Wifi = 1,
-//   Cell = 2,
-//   Ethernet = 3,
-//   Bluetooth = 4,
-//   VPN = 5,
-// }
-//
-// impl NetworkTypePB {
-//   pub fn is_connect(&self) -> bool {
-//     match self {
-//       NetworkTypePB::Unknown | NetworkTypePB::Bluetooth => false,
-//       NetworkTypePB::Wifi | NetworkTypePB::Cell | NetworkTypePB::Ethernet | NetworkTypePB::VPN => {
-//         true
-//       },
-//     }
-//   }
-// }
-//
-// #[derive(ProtoBuf, Debug, Default, Clone)]
-// pub struct NetworkStatePB {
-//   #[pb(index = 1)]
-//   pub ty: NetworkTypePB,
-// }

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

@@ -1,5 +0,0 @@
-use lib_dispatch::prelude::*;
-
-pub fn init() -> AFPlugin {
-  AFPlugin::new().name("Flowy-Network")
-}

+ 0 - 3
frontend/rust-lib/flowy-net/src/handlers/mod.rs

@@ -1,3 +0,0 @@
-// pub async fn update_network_ty(_data: AFPluginData<NetworkStatePB>) -> Result<(), FlowyError> {
-//   Ok(())
-// }

+ 0 - 3
frontend/rust-lib/flowy-net/src/lib.rs

@@ -1,3 +0,0 @@
-pub mod entities;
-pub mod event_map;
-mod handlers;

+ 1 - 1
frontend/rust-lib/flowy-notification/Cargo.toml

@@ -13,7 +13,7 @@ bytes = { version = "1.4" }
 serde = "1.0"
 
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
-lib-dispatch = { path = "../lib-dispatch" }
+lib-dispatch = { workspace = true }
 
 [build-dependencies]
 flowy-codegen = { path = "../../../shared-lib/flowy-codegen" }

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

@@ -6,5 +6,5 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-flowy-error = { path = "../flowy-error" }
+flowy-error  = { workspace = true }
 serde = { version = "1.0", features = ["derive"] }

+ 11 - 10
frontend/rust-lib/flowy-server/Cargo.toml

@@ -23,25 +23,26 @@ bytes = { version = "1.0.1", features = ["serde"] }
 tokio-retry = "0.3"
 anyhow = "1.0"
 uuid = { version = "1.3.3", features = ["v4"] }
-chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
+chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
 collab = { version = "0.1.0" }
-collab-plugins = { version = "0.1.0" }
+collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
 collab-document = { version = "0.1.0" }
 collab-define = { version = "0.1.0" }
 hex = "0.4.3"
 postgrest = "1.0"
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-flowy-user-deps = { path = "../flowy-user-deps" }
-flowy-folder-deps = { path = "../flowy-folder-deps" }
-flowy-database-deps = { path = "../flowy-database-deps" }
-flowy-document-deps = { path = "../flowy-document-deps" }
-flowy-error = { path = "../flowy-error", features = ["impl_from_postgres", "impl_from_serde", "impl_from_reqwest", "impl_from_url"] }
-flowy-server-config = { path = "../flowy-server-config" }
-flowy-encrypt = { path = "../flowy-encrypt" }
-flowy-storage = { path = "../flowy-storage" }
+flowy-user-deps = { workspace = true }
+flowy-folder-deps  = { workspace = true }
+flowy-database-deps = { workspace = true }
+flowy-document-deps = { workspace = true }
+flowy-error = { workspace = true, features = ["impl_from_postgres", "impl_from_serde", "impl_from_reqwest", "impl_from_url", "impl_from_appflowy_cloud"] }
+flowy-server-config = { workspace = true }
+flowy-encrypt = { workspace = true }
+flowy-storage = { workspace = true }
 mime_guess = "2.0"
 url = "2.4"
 tokio-util = "0.7"
+client-api = { version = "0.1.0" }
 
 [dev-dependencies]
 uuid = { version = "1.3.3", features = ["v4"] }

+ 1 - 37
frontend/rust-lib/flowy-server/src/af_cloud/configuration.rs

@@ -20,7 +20,7 @@ pub fn appflowy_cloud_server_configuration() -> Result<AFCloudConfiguration, con
   settings.merge(config::File::from_str(base, FileFormat::Yaml).required(true))?;
 
   let environment: Environment = std::env::var("APP_ENVIRONMENT")
-    .unwrap_or_else(|_| "local".into())
+    .unwrap_or_else(|_| "local".to_owned())
     .try_into()
     .expect("Failed to parse APP_ENVIRONMENT.");
 
@@ -43,42 +43,6 @@ impl AFCloudConfiguration {
     format!("{}://{}:{}", self.http_scheme, self.host, self.port)
   }
 
-  pub fn sign_up_url(&self) -> String {
-    format!("{}/api/register", self.base_url())
-  }
-
-  pub fn sign_in_url(&self) -> String {
-    format!("{}/api/auth", self.base_url())
-  }
-
-  pub fn sign_out_url(&self) -> String {
-    format!("{}/api/auth", self.base_url())
-  }
-
-  pub fn user_profile_url(&self) -> String {
-    format!("{}/api/user", self.base_url())
-  }
-
-  pub fn workspace_url(&self) -> String {
-    format!("{}/api/workspace", self.base_url())
-  }
-
-  pub fn app_url(&self) -> String {
-    format!("{}/api/app", self.base_url())
-  }
-
-  pub fn view_url(&self) -> String {
-    format!("{}/api/view", self.base_url())
-  }
-
-  pub fn doc_url(&self) -> String {
-    format!("{}/api/doc", self.base_url())
-  }
-
-  pub fn trash_url(&self) -> String {
-    format!("{}/api/trash", self.base_url())
-  }
-
   pub fn ws_addr(&self) -> String {
     format!("{}://{}:{}/ws", self.ws_scheme, self.host, self.port)
   }

+ 7 - 2
frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs

@@ -6,9 +6,14 @@ use flowy_database_deps::cloud::{
 };
 use lib_infra::future::FutureResult;
 
-pub(crate) struct AFCloudDatabaseCloudServiceImpl();
+use crate::af_cloud::AFServer;
 
-impl DatabaseCloudService for AFCloudDatabaseCloudServiceImpl {
+pub(crate) struct AFCloudDatabaseCloudServiceImpl<T>(pub T);
+
+impl<T> DatabaseCloudService for AFCloudDatabaseCloudServiceImpl<T>
+where
+  T: AFServer,
+{
   fn get_collab_update(
     &self,
     _object_id: &str,

+ 7 - 2
frontend/rust-lib/flowy-server/src/af_cloud/impls/document.rs

@@ -3,9 +3,14 @@ use anyhow::Error;
 use flowy_document_deps::cloud::*;
 use lib_infra::future::FutureResult;
 
-pub(crate) struct AFCloudDocumentCloudServiceImpl();
+use crate::af_cloud::AFServer;
 
-impl DocumentCloudService for AFCloudDocumentCloudServiceImpl {
+pub(crate) struct AFCloudDocumentCloudServiceImpl<T>(pub T);
+
+impl<T> DocumentCloudService for AFCloudDocumentCloudServiceImpl<T>
+where
+  T: AFServer,
+{
   fn get_document_updates(&self, _document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
     FutureResult::new(async move { Ok(vec![]) })
   }

+ 12 - 18
frontend/rust-lib/flowy-server/src/af_cloud/impls/folder.rs

@@ -1,24 +1,18 @@
 use anyhow::Error;
 
-use flowy_folder_deps::cloud::{
-  gen_workspace_id, FolderCloudService, FolderData, FolderSnapshot, Workspace,
-};
+use flowy_folder_deps::cloud::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
 use lib_infra::future::FutureResult;
-use lib_infra::util::timestamp;
-
-pub(crate) struct AFCloudFolderCloudServiceImpl();
-
-impl FolderCloudService for AFCloudFolderCloudServiceImpl {
-  fn create_workspace(&self, _uid: i64, name: &str) -> FutureResult<Workspace, Error> {
-    let name = name.to_string();
-    FutureResult::new(async move {
-      Ok(Workspace {
-        id: gen_workspace_id().to_string(),
-        name: name.to_string(),
-        child_views: Default::default(),
-        created_at: timestamp(),
-      })
-    })
+
+use crate::af_cloud::AFServer;
+
+pub(crate) struct AFCloudFolderCloudServiceImpl<T>(pub T);
+
+impl<T> FolderCloudService for AFCloudFolderCloudServiceImpl<T>
+where
+  T: AFServer,
+{
+  fn create_workspace(&self, _uid: i64, _name: &str) -> FutureResult<Workspace, Error> {
+    FutureResult::new(async move { todo!() })
   }
 
   fn get_folder_data(&self, _workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {

+ 47 - 106
frontend/rust-lib/flowy-server/src/af_cloud/impls/user.rs

@@ -1,96 +1,60 @@
+use std::sync::Arc;
+
 use anyhow::Error;
 use collab_define::CollabObject;
 
-use flowy_error::{ErrorCode, FlowyError};
+use flowy_error::FlowyError;
 use flowy_user_deps::cloud::UserCloudService;
 use flowy_user_deps::entities::*;
 use lib_infra::box_any::BoxAny;
 use lib_infra::future::FutureResult;
 
-use crate::af_cloud::configuration::{AFCloudConfiguration, HEADER_TOKEN};
-use crate::request::HttpRequestBuilder;
+use crate::af_cloud::{AFCloudClient, AFServer};
 
-pub(crate) struct AFCloudUserAuthServiceImpl {
-  config: AFCloudConfiguration,
+pub(crate) struct AFCloudUserAuthServiceImpl<T> {
+  server: T,
 }
 
-impl AFCloudUserAuthServiceImpl {
-  pub(crate) fn new(config: AFCloudConfiguration) -> Self {
-    Self { config }
+impl<T> AFCloudUserAuthServiceImpl<T> {
+  pub(crate) fn new(server: T) -> Self {
+    Self { server }
   }
 }
 
-impl UserCloudService for AFCloudUserAuthServiceImpl {
+impl<T> UserCloudService for AFCloudUserAuthServiceImpl<T>
+where
+  T: AFServer,
+{
   fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, Error> {
-    let url = self.config.sign_up_url();
+    let try_get_client = self.server.try_get_client();
     FutureResult::new(async move {
       let params = params.unbox_or_error::<SignUpParams>()?;
-      let resp = user_sign_up_request(params, &url).await?;
+      let resp = user_sign_up_request(try_get_client?, params).await?;
       Ok(resp)
     })
   }
 
-  fn sign_in(&self, params: BoxAny) -> FutureResult<SignInResponse, Error> {
-    let url = self.config.sign_in_url();
-    FutureResult::new(async move {
-      let params = params.unbox_or_error::<SignInParams>()?;
-      let resp = user_sign_in_request(params, &url).await?;
-      Ok(resp)
-    })
+  fn sign_in(&self, _params: BoxAny) -> FutureResult<SignInResponse, Error> {
+    todo!()
   }
 
-  fn sign_out(&self, token: Option<String>) -> FutureResult<(), Error> {
-    match token {
-      None => FutureResult::new(async {
-        Err(FlowyError::new(ErrorCode::InvalidParams, "Token should not be empty").into())
-      }),
-      Some(token) => {
-        let token = token;
-        let url = self.config.sign_out_url();
-        FutureResult::new(async move {
-          let _ = user_sign_out_request(&token, &url).await;
-          Ok(())
-        })
-      },
-    }
+  fn sign_out(&self, _token: Option<String>) -> FutureResult<(), Error> {
+    todo!()
   }
 
   fn update_user(
     &self,
-    credential: UserCredentials,
-    params: UpdateUserProfileParams,
+    _credential: UserCredentials,
+    _params: UpdateUserProfileParams,
   ) -> FutureResult<(), Error> {
-    match credential.token {
-      None => FutureResult::new(async {
-        Err(FlowyError::new(ErrorCode::InvalidParams, "Token should not be empty").into())
-      }),
-      Some(token) => {
-        let token = token;
-        let url = self.config.user_profile_url();
-        FutureResult::new(async move {
-          update_user_profile_request(&token, params, &url).await?;
-          Ok(())
-        })
-      },
-    }
+    todo!()
   }
 
   fn get_user_profile(
     &self,
-    credential: UserCredentials,
+    _credential: UserCredentials,
   ) -> FutureResult<Option<UserProfile>, Error> {
-    let url = self.config.user_profile_url();
-    FutureResult::new(async move {
-      match credential.token {
-        None => {
-          Err(FlowyError::new(ErrorCode::UnexpectedEmpty, "Token should not be empty").into())
-        },
-        Some(token) => {
-          let profile = get_user_profile_request(&token, &url).await?;
-          Ok(Some(profile))
-        },
-      }
-    })
+    todo!()
   }
 
   fn get_user_workspaces(
@@ -145,53 +109,30 @@ impl UserCloudService for AFCloudUserAuthServiceImpl {
 }
 
 pub async fn user_sign_up_request(
+  client: Arc<AFCloudClient>,
   params: SignUpParams,
-  url: &str,
 ) -> Result<SignUpResponse, FlowyError> {
-  let response = request_builder().post(url).json(params)?.response().await?;
-  Ok(response)
-}
-
-pub async fn user_sign_in_request(
-  params: SignInParams,
-  url: &str,
-) -> Result<SignInResponse, FlowyError> {
-  let response = request_builder().post(url).json(params)?.response().await?;
-  Ok(response)
-}
-
-pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), FlowyError> {
-  request_builder()
-    .delete(url)
-    .header(HEADER_TOKEN, token)
-    .send()
-    .await?;
-  Ok(())
-}
-
-pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfile, FlowyError> {
-  let user_profile = request_builder()
-    .get(url)
-    .header(HEADER_TOKEN, token)
-    .response()
+  client
+    .read()
+    .await
+    .sign_up(&params.email, &params.password)
     .await?;
-  Ok(user_profile)
-}
-
-pub async fn update_user_profile_request(
-  token: &str,
-  params: UpdateUserProfileParams,
-  url: &str,
-) -> Result<(), FlowyError> {
-  request_builder()
-    .patch(url)
-    .header(HEADER_TOKEN, token)
-    .json(params)?
-    .send()
-    .await?;
-  Ok(())
-}
-
-fn request_builder() -> HttpRequestBuilder {
-  HttpRequestBuilder::new()
+  todo!()
+  // tracing::info!("User signed up: {:?}", user);
+  // match user.confirmed_at {
+  //   Some(_) => {
+  //       // User is already confirmed, help her/him to sign in
+  //       let token = client.sign_in_password(&params.email, &params.password).await?;
+  //
+  //       // TODO:
+  //       // Query workspace list
+  //       // Query user profile
+  //
+  //       todo!()
+  //   },
+  //   None => Err(FlowyError::new(
+  //     ErrorCode::AwaitingEmailConfirmation,
+  //     "Awaiting email confirmation".to_string(),
+  //   )),
+  // }
 }

+ 156 - 10
frontend/rust-lib/flowy-server/src/af_cloud/server.rs

@@ -1,13 +1,19 @@
+use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 
-use collab_define::CollabObject;
-use collab_plugins::cloud_storage::RemoteCollabStorage;
+use anyhow::Error;
+use client_api::notify::{TokenState, TokenStateReceiver};
+use client_api::ws::{BusinessID, WSClient, WSClientConfig, WebSocketChannel};
+use client_api::Client;
+use tokio::sync::RwLock;
 
 use flowy_database_deps::cloud::DatabaseCloudService;
 use flowy_document_deps::cloud::DocumentCloudService;
+use flowy_error::{ErrorCode, FlowyError};
 use flowy_folder_deps::cloud::FolderCloudService;
 use flowy_storage::FileStorageService;
 use flowy_user_deps::cloud::UserCloudService;
+use lib_infra::future::FutureResult;
 
 use crate::af_cloud::configuration::AFCloudConfiguration;
 use crate::af_cloud::impls::{
@@ -16,38 +22,178 @@ use crate::af_cloud::impls::{
 };
 use crate::AppFlowyServer;
 
+pub(crate) type AFCloudClient = RwLock<client_api::Client>;
+
 pub struct AFCloudServer {
+  #[allow(dead_code)]
   pub(crate) config: AFCloudConfiguration,
+  pub(crate) client: Arc<AFCloudClient>,
+  enable_sync: AtomicBool,
+  #[allow(dead_code)]
+  device_id: Arc<parking_lot::RwLock<String>>,
+  ws_client: Arc<RwLock<WSClient>>,
 }
 
 impl AFCloudServer {
-  pub fn new(config: AFCloudConfiguration) -> Self {
-    Self { config }
+  pub fn new(
+    config: AFCloudConfiguration,
+    enable_sync: bool,
+    device_id: Arc<parking_lot::RwLock<String>>,
+  ) -> Self {
+    let http_client = reqwest::Client::new();
+    let api_client = client_api::Client::from(http_client, &config.base_url(), &config.ws_addr());
+    let token_state_rx = api_client.subscribe_token_state();
+    let enable_sync = AtomicBool::new(enable_sync);
+
+    let ws_client = WSClient::new(WSClientConfig {
+      buffer_capacity: 100,
+      ping_per_secs: 2,
+      retry_connect_per_pings: 5,
+    });
+    let ws_client = Arc::new(RwLock::new(ws_client));
+    let api_client = Arc::new(RwLock::new(api_client));
+
+    spawn_ws_conn(&device_id, token_state_rx, &ws_client, &api_client);
+    Self {
+      config,
+      client: api_client,
+      enable_sync,
+      device_id,
+      ws_client,
+    }
+  }
+
+  fn get_client(&self) -> Option<Arc<AFCloudClient>> {
+    if self.enable_sync.load(Ordering::SeqCst) {
+      Some(self.client.clone())
+    } else {
+      None
+    }
   }
 }
 
 impl AppFlowyServer for AFCloudServer {
+  fn set_enable_sync(&self, uid: i64, enable: bool) {
+    tracing::info!("{} cloud sync: {}", uid, enable);
+    self.enable_sync.store(enable, Ordering::SeqCst);
+  }
   fn user_service(&self) -> Arc<dyn UserCloudService> {
-    Arc::new(AFCloudUserAuthServiceImpl::new(self.config.clone()))
+    let server = AFServerImpl(self.get_client());
+    Arc::new(AFCloudUserAuthServiceImpl::new(server))
   }
 
   fn folder_service(&self) -> Arc<dyn FolderCloudService> {
-    Arc::new(AFCloudFolderCloudServiceImpl())
+    let server = AFServerImpl(self.get_client());
+    Arc::new(AFCloudFolderCloudServiceImpl(server))
   }
 
   fn database_service(&self) -> Arc<dyn DatabaseCloudService> {
-    Arc::new(AFCloudDatabaseCloudServiceImpl())
+    let server = AFServerImpl(self.get_client());
+    Arc::new(AFCloudDatabaseCloudServiceImpl(server))
   }
 
   fn document_service(&self) -> Arc<dyn DocumentCloudService> {
-    Arc::new(AFCloudDocumentCloudServiceImpl())
+    let server = AFServerImpl(self.get_client());
+    Arc::new(AFCloudDocumentCloudServiceImpl(server))
   }
 
-  fn collab_storage(&self, _collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
-    None
+  fn collab_ws_channel(
+    &self,
+    object_id: &str,
+  ) -> FutureResult<Option<Arc<WebSocketChannel>>, anyhow::Error> {
+    if self.enable_sync.load(Ordering::SeqCst) {
+      let object_id = object_id.to_string();
+      let weak_ws_client = Arc::downgrade(&self.ws_client);
+      FutureResult::new(async move {
+        match weak_ws_client.upgrade() {
+          None => {
+            tracing::warn!("🟡Collab WS client is dropped");
+            Ok(None)
+          },
+          Some(ws_client) => Ok(
+            ws_client
+              .read()
+              .await
+              .subscribe(BusinessID::CollabId, object_id)
+              .await
+              .ok(),
+          ),
+        }
+      })
+    } else {
+      FutureResult::new(async { Ok(None) })
+    }
   }
 
   fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> {
     None
   }
 }
+
+/// Spawns a new asynchronous task to handle WebSocket connections based on token state.
+///
+/// This function listens to the `token_state_rx` channel for token state updates. Depending on the
+/// received state, it either refreshes the WebSocket connection or disconnects from it.
+fn spawn_ws_conn(
+  device_id: &Arc<parking_lot::RwLock<String>>,
+  mut token_state_rx: TokenStateReceiver,
+  ws_client: &Arc<RwLock<WSClient>>,
+  api_client: &Arc<RwLock<Client>>,
+) {
+  let weak_device_id = Arc::downgrade(device_id);
+  let weak_ws_client = Arc::downgrade(ws_client);
+  let weak_api_client = Arc::downgrade(api_client);
+  tokio::spawn(async move {
+    while let Ok(token_state) = token_state_rx.recv().await {
+      tracing::info!("🟢Token state: {:?}", token_state);
+      match token_state {
+        TokenState::Refresh => {
+          if let (Some(api_client), Some(ws_client), Some(device_id)) = (
+            weak_api_client.upgrade(),
+            weak_ws_client.upgrade(),
+            weak_device_id.upgrade(),
+          ) {
+            let device_id = device_id.read().clone();
+            if let Ok(ws_addr) = api_client.read().await.ws_url(&device_id) {
+              tracing::info!("🟢Connecting to websocket");
+              let _ = ws_client.write().await.connect(ws_addr).await;
+            }
+          }
+        },
+        TokenState::Invalid => {
+          if let Some(ws_client) = weak_ws_client.upgrade() {
+            tracing::info!("🟡Disconnecting from websocket");
+            ws_client.write().await.disconnect().await;
+          }
+        },
+      }
+    }
+  });
+}
+
+pub trait AFServer: Send + Sync + 'static {
+  fn get_client(&self) -> Option<Arc<AFCloudClient>>;
+  fn try_get_client(&self) -> Result<Arc<AFCloudClient>, Error>;
+}
+
+#[derive(Clone)]
+pub struct AFServerImpl(pub Option<Arc<AFCloudClient>>);
+
+impl AFServer for AFServerImpl {
+  fn get_client(&self) -> Option<Arc<AFCloudClient>> {
+    self.0.clone()
+  }
+
+  fn try_get_client(&self) -> Result<Arc<AFCloudClient>, Error> {
+    match self.0.clone() {
+      None => Err(
+        FlowyError::new(
+          ErrorCode::DataSyncRequired,
+          "Data Sync is disabled, please enable it first",
+        )
+        .into(),
+      ),
+      Some(client) => Ok(client),
+    }
+  }
+}

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

@@ -2,7 +2,7 @@ pub use server::*;
 
 pub mod af_cloud;
 pub mod local_server;
-mod request;
+// mod request;
 mod response;
 mod server;
 pub mod supabase;

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

@@ -1,7 +1,5 @@
 use std::sync::Arc;
 
-use collab_define::CollabObject;
-use collab_plugins::cloud_storage::RemoteCollabStorage;
 use parking_lot::RwLock;
 use tokio::sync::mpsc;
 
@@ -70,10 +68,6 @@ impl AppFlowyServer for LocalServer {
     Arc::new(LocalServerDocumentCloudServiceImpl())
   }
 
-  fn collab_storage(&self, _collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
-    None
-  }
-
   fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> {
     None
   }

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

@@ -1,5 +1,6 @@
 use std::sync::Arc;
 
+use client_api::ws::WebSocketChannel;
 use collab_define::CollabObject;
 use collab_plugins::cloud_storage::RemoteCollabStorage;
 use parking_lot::RwLock;
@@ -9,6 +10,7 @@ use flowy_document_deps::cloud::DocumentCloudService;
 use flowy_folder_deps::cloud::FolderCloudService;
 use flowy_storage::FileStorageService;
 use flowy_user_deps::cloud::UserCloudService;
+use lib_infra::future::FutureResult;
 
 pub trait AppFlowyEncryption: Send + Sync + 'static {
   fn get_secret(&self) -> Option<String>;
@@ -85,7 +87,16 @@ pub trait AppFlowyServer: Send + Sync + 'static {
   /// # Returns
   ///
   /// An `Option` that might contain an `Arc` wrapping the `RemoteCollabStorage` interface.
-  fn collab_storage(&self, collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>>;
+  fn collab_storage(&self, _collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
+    None
+  }
+
+  fn collab_ws_channel(
+    &self,
+    _object_id: &str,
+  ) -> FutureResult<Option<Arc<WebSocketChannel>>, anyhow::Error> {
+    FutureResult::new(async { Ok(None) })
+  }
 
   fn file_storage(&self) -> Option<Arc<dyn FileStorageService>>;
 }

+ 22 - 22
frontend/rust-lib/flowy-server/src/supabase/api/collab_storage.rs

@@ -62,8 +62,11 @@ where
 
   async fn get_all_updates(&self, object: &CollabObject) -> Result<Vec<Vec<u8>>, Error> {
     let postgrest = self.server.try_get_weak_postgrest()?;
-    let action =
-      FetchObjectUpdateAction::new(object.object_id.clone(), object.ty.clone(), postgrest);
+    let action = FetchObjectUpdateAction::new(
+      object.object_id.clone(),
+      object.collab_type.clone(),
+      postgrest,
+    );
     let updates = action.run().await?;
     Ok(updates)
   }
@@ -140,10 +143,14 @@ where
     update: Vec<u8>,
   ) -> Result<(), Error> {
     if let Some(postgrest) = self.server.get_postgrest() {
-      let workspace_id = object.get_workspace_id().ok_or(anyhow::anyhow!(
-        "Can't get the workspace id in CollabObject"
-      ))?;
-      send_update(workspace_id, object, update, &postgrest, &self.secret()).await?;
+      send_update(
+        object.workspace_id.clone(),
+        object,
+        update,
+        &postgrest,
+        &self.secret(),
+      )
+      .await?;
     }
 
     Ok(())
@@ -156,16 +163,14 @@ where
     init_update: Vec<u8>,
   ) -> Result<(), Error> {
     let postgrest = self.server.try_get_postgrest()?;
-    let workspace_id = object
-      .get_workspace_id()
-      .ok_or(anyhow::anyhow!("Invalid workspace id"))?;
 
-    let update_items = get_updates_from_server(&object.object_id, &object.ty, &postgrest).await?;
+    let update_items =
+      get_updates_from_server(&object.object_id, &object.collab_type, &postgrest).await?;
 
     // If the update_items is empty, we can send the init_update directly
     if update_items.is_empty() {
       send_update(
-        workspace_id,
+        object.workspace_id.clone(),
         object,
         init_update,
         &postgrest,
@@ -197,18 +202,13 @@ pub(crate) async fn flush_collab_with_update(
 ) -> Result<(), Error> {
   // 2.Merge the updates into one and then delete the merged updates
   let merge_result = spawn_blocking(move || merge_updates(update_items, update)).await??;
-
-  let workspace_id = object
-    .get_workspace_id()
-    .ok_or(anyhow::anyhow!("Invalid workspace id"))?;
-
   let value_size = merge_result.new_update.len() as i32;
   let md5 = md5(&merge_result.new_update);
 
   tracing::trace!(
     "Flush collab id:{} type:{} is_encrypt: {}",
     object.object_id,
-    object.ty,
+    object.collab_type,
     secret.is_some()
   );
   let (new_update, encrypt) =
@@ -219,11 +219,11 @@ pub(crate) async fn flush_collab_with_update(
     .insert("encrypt", encrypt)
     .insert("md5", md5)
     .insert("value_size", value_size)
-    .insert("partition_key", partition_key(&object.ty))
+    .insert("partition_key", partition_key(&object.collab_type))
     .insert("uid", object.uid)
-    .insert("workspace_id", workspace_id)
+    .insert("workspace_id", &object.workspace_id)
     .insert("removed_keys", merge_result.merged_keys)
-    .insert("did", object.get_device_id())
+    .insert("did", &object.device_id)
     .build();
 
   postgrest
@@ -247,13 +247,13 @@ pub(crate) async fn send_update(
   let (update, encrypt) = SupabaseBinaryColumnEncoder::encode(update, encryption_secret)?;
   let builder = InsertParamsBuilder::new()
     .insert("oid", object.object_id.clone())
-    .insert("partition_key", partition_key(&object.ty))
+    .insert("partition_key", partition_key(&object.collab_type))
     .insert("value", update)
     .insert("encrypt", encrypt)
     .insert("uid", object.uid)
     .insert("md5", md5)
     .insert("workspace_id", workspace_id)
-    .insert("did", object.get_device_id())
+    .insert("did", &object.device_id)
     .insert("value_size", value_size);
 
   let params = builder.build();

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

@@ -150,7 +150,7 @@ pub async fn create_snapshot(
     .insert(
       InsertParamsBuilder::new()
         .insert(AF_COLLAB_SNAPSHOT_OID_COLUMN, object.object_id.clone())
-        .insert("name", object.ty.to_string())
+        .insert("name", object.collab_type.to_string())
         .insert(AF_COLLAB_SNAPSHOT_ENCRYPT_COLUMN, encrypt)
         .insert(AF_COLLAB_SNAPSHOT_BLOB_COLUMN, snapshot)
         .insert(AF_COLLAB_SNAPSHOT_BLOB_SIZE_COLUMN, value_size)

+ 6 - 3
frontend/rust-lib/flowy-server/src/supabase/api/user.rs

@@ -292,9 +292,12 @@ where
             .upgrade()
             .ok_or(anyhow::anyhow!("postgrest is not available"))?;
 
-          let updates =
-            get_updates_from_server(&collab_object.object_id, &collab_object.ty, &postgrest)
-              .await?;
+          let updates = get_updates_from_server(
+            &collab_object.object_id,
+            &collab_object.collab_type,
+            &postgrest,
+          )
+          .await?;
 
           flush_collab_with_update(
             &collab_object,

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

@@ -9,7 +9,7 @@ mod supabase_test;
 pub fn setup_log() {
   static START: Once = Once::new();
   START.call_once(|| {
-    let level = "debug";
+    let level = "trace";
     let mut filters = vec![];
     filters.push(format!("flowy_server={}", level));
     std::env::set_var("RUST_LOG", filters.join(","));

+ 8 - 8
frontend/rust-lib/flowy-server/tests/supabase_test/database_test.rs

@@ -10,7 +10,7 @@ use crate::supabase_test::util::{
 };
 
 #[tokio::test]
-async fn supabase_create_workspace_test() {
+async fn supabase_create_database_test() {
   if get_supabase_ci_config().is_none() {
     return;
   }
@@ -27,13 +27,13 @@ async fn supabase_create_workspace_test() {
   for _i in 0..3 {
     let row_id = uuid::Uuid::new_v4().to_string();
     row_ids.push(row_id.clone());
-    let collab_object = CollabObject {
-      object_id: row_id,
-      uid: user.user_id,
-      ty: CollabType::DatabaseRow,
-      meta: Default::default(),
-    }
-    .with_workspace_id(user.latest_workspace.id.clone());
+    let collab_object = CollabObject::new(
+      user.user_id,
+      row_id,
+      CollabType::DatabaseRow,
+      user.latest_workspace.id.clone(),
+      "fake_device_id".to_string(),
+    );
     collab_service
       .send_update(&collab_object, 0, vec![1, 2, 3])
       .await

+ 21 - 21
frontend/rust-lib/flowy-server/tests/supabase_test/folder_test.rs

@@ -39,13 +39,13 @@ async fn supabase_get_folder_test() {
   let params = third_party_sign_up_param(uuid);
   let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
 
-  let collab_object = CollabObject {
-    object_id: user.latest_workspace.id.clone(),
-    uid: user.user_id,
-    ty: CollabType::Folder,
-    meta: Default::default(),
-  }
-  .with_workspace_id(user.latest_workspace.id.clone());
+  let collab_object = CollabObject::new(
+    user.user_id,
+    user.latest_workspace.id.clone(),
+    CollabType::Folder,
+    user.latest_workspace.id.clone(),
+    "fake_device_id".to_string(),
+  );
 
   let doc = Doc::with_client_id(1);
   let map = { doc.get_or_insert_map("map") };
@@ -113,13 +113,13 @@ async fn supabase_duplicate_updates_test() {
   let params = third_party_sign_up_param(uuid);
   let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
 
-  let collab_object = CollabObject {
-    object_id: user.latest_workspace.id.clone(),
-    uid: user.user_id,
-    ty: CollabType::Folder,
-    meta: Default::default(),
-  }
-  .with_workspace_id(user.latest_workspace.id.clone());
+  let collab_object = CollabObject::new(
+    user.user_id,
+    user.latest_workspace.id.clone(),
+    CollabType::Folder,
+    user.latest_workspace.id.clone(),
+    "fake_device_id".to_string(),
+  );
   let doc = Doc::with_client_id(1);
   let map = { doc.get_or_insert_map("map") };
   let mut duplicated_updates = vec![];
@@ -220,13 +220,13 @@ async fn supabase_diff_state_vector_test() {
   let params = third_party_sign_up_param(uuid);
   let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
 
-  let collab_object = CollabObject {
-    object_id: user.latest_workspace.id.clone(),
-    uid: user.user_id,
-    ty: CollabType::Folder,
-    meta: Default::default(),
-  }
-  .with_workspace_id(user.latest_workspace.id.clone());
+  let collab_object = CollabObject::new(
+    user.user_id,
+    user.latest_workspace.id.clone(),
+    CollabType::Folder,
+    user.latest_workspace.id.clone(),
+    "fake_device_id".to_string(),
+  );
   let doc = Doc::with_client_id(1);
   let map = { doc.get_or_insert_map("map") };
   let array = { doc.get_or_insert_array("array") };

+ 1 - 1
frontend/rust-lib/flowy-storage/Cargo.toml

@@ -14,4 +14,4 @@ bytes = "1.0.1"
 mime_guess = "2.0"
 lib-infra = { path = "../../../shared-lib/lib-infra" }
 url = "2.2.2"
-flowy-error = { path = "../flowy-error", features = ["impl_from_reqwest"] }
+flowy-error = { workspace = true, features = ["impl_from_reqwest"] }

+ 12 - 12
frontend/rust-lib/flowy-test/Cargo.toml

@@ -6,24 +6,23 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-flowy-core = { path = "../flowy-core" }
-flowy-user = { path = "../flowy-user"}
-flowy-user-deps = { path = "../flowy-user-deps"}
-flowy-net = { path = "../flowy-net"}
+flowy-core = { workspace = true }
+flowy-user = { workspace = true }
+flowy-user-deps = { workspace = true }
 flowy-folder2 = { path = "../flowy-folder2", features = ["test_helper"] }
-flowy-folder-deps = { path = "../flowy-folder-deps" }
+flowy-folder-deps = { workspace = true }
 flowy-database2 = { path = "../flowy-database2" }
-flowy-database-deps = { path = "../flowy-database-deps" }
+flowy-database-deps = { workspace = true }
 flowy-document2 = { path = "../flowy-document2" }
-flowy-document-deps = { path = "../flowy-document-deps" }
-flowy-encrypt = { path = "../flowy-encrypt" }
-lib-dispatch = { path = "../lib-dispatch" }
+flowy-document-deps = { workspace = true }
+flowy-encrypt = { workspace = true }
+lib-dispatch = { workspace = true }
 lib-infra = { path = "../../../shared-lib/lib-infra" }
 flowy-server = { path = "../flowy-server" }
-flowy-server-config = { path = "../flowy-server-config" }
-flowy-notification = { path = "../flowy-notification" }
+flowy-server-config = { workspace = true }
+flowy-notification  = { workspace = true }
 anyhow = "1.0.71"
-flowy-storage = { path = "../flowy-storage" }
+flowy-storage = { workspace = true }
 
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
@@ -46,6 +45,7 @@ collab-document = { version = "0.1.0" }
 collab-folder = { version = "0.1.0" }
 collab-database = { version = "0.1.0" }
 collab-plugins = { version = "0.1.0" }
+collab-define = { version = "0.1.0" }
 assert-json-diff = "2.0.2"
 tokio-postgres = { version = "0.7.8" }
 zip = "0.6.6"

+ 1 - 1
frontend/rust-lib/flowy-test/tests/database/local_test/test.rs

@@ -527,7 +527,7 @@ async fn update_date_cell_event_test() {
   let error = test
     .update_date_cell(DateChangesetPB {
       cell_id: cell_path,
-      date: Some(timestamp.clone()),
+      date: Some(timestamp),
       time: None,
       include_time: None,
       clear_flag: None,

+ 1 - 1
frontend/rust-lib/flowy-test/tests/database/supabase_test/helper.rs

@@ -5,7 +5,7 @@ use collab::core::collab::MutexCollab;
 use collab::core::origin::CollabOrigin;
 use collab::preclude::updates::decoder::Decode;
 use collab::preclude::{merge_updates_v1, JsonValue, Update};
-use collab_plugins::cloud_storage::CollabType;
+use collab_define::CollabType;
 
 use flowy_database2::entities::{DatabasePB, DatabaseViewIdPB, RepeatedDatabaseSnapshotPB};
 use flowy_database2::event_map::DatabaseEvent::*;

+ 2 - 1
frontend/rust-lib/flowy-test/tests/user/supabase_test/auth_test.rs

@@ -2,6 +2,7 @@ use std::collections::HashMap;
 
 use assert_json_diff::assert_json_eq;
 use collab_database::rows::database_row_document_id_from_row_id;
+use collab_define::CollabType;
 use collab_document::blocks::DocumentData;
 use collab_folder::core::FolderData;
 use nanoid::nanoid;
@@ -9,7 +10,7 @@ use serde_json::json;
 
 use flowy_core::DEFAULT_NAME;
 use flowy_encrypt::decrypt_text;
-use flowy_server::supabase::define::{CollabType, USER_EMAIL, USER_UUID};
+use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
 use flowy_test::document::document_event::DocumentEventTest;
 use flowy_test::event_builder::EventBuilder;
 use flowy_test::FlowyCoreTest;

+ 3 - 3
frontend/rust-lib/flowy-user-deps/Cargo.toml

@@ -7,12 +7,12 @@ edition = "2021"
 
 [dependencies]
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-flowy-error = { path = "../flowy-error" }
+flowy-error  = { workspace = true }
 uuid = { version = "1.3.3", features = ["v4"] }
 serde = { version = "1.0", features = ["derive"] }
 collab-define = { version = "0.1.0" }
-serde_json = {version = "1.0"}
+serde_json = { version = "1.0"}
 serde_repr = "0.1"
-chrono = { version = "0.4.22", default-features = false, features = ["clock", "serde"] }
+chrono = { version = "0.4.27", default-features = false, features = ["clock", "serde"] }
 anyhow = "1.0.71"
 tokio = { version = "1.26", features = ["sync"] }

+ 10 - 10
frontend/rust-lib/flowy-user/Cargo.toml

@@ -7,22 +7,22 @@ edition = "2018"
 
 [dependencies]
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
-flowy-sqlite = { path = "../flowy-sqlite", optional = true }
-flowy-encrypt = { path = "../flowy-encrypt" }
-flowy-error = { path = "../flowy-error", features = ["impl_from_sqlite", "impl_from_dispatch_error"] }
-flowy-folder-deps = { path = "../flowy-folder-deps" }
+flowy-sqlite = { workspace = true, optional = true }
+flowy-encrypt = { workspace = true }
+flowy-error = { workspace = true, features = ["impl_from_sqlite", "impl_from_dispatch_error"] }
+flowy-folder-deps = { workspace = true }
 lib-infra = { path = "../../../shared-lib/lib-infra" }
-flowy-notification = { path = "../flowy-notification" }
-flowy-server-config = { path = "../flowy-server-config" }
-lib-dispatch = { path = "../lib-dispatch" }
-appflowy-integrate = { version = "0.1.0" }
+flowy-notification  = { workspace = true }
+flowy-server-config = { workspace = true }
+lib-dispatch = { workspace = true }
+collab-integrate = { workspace = true }
 collab = { version = "0.1.0" }
 collab-folder = { version = "0.1.0" }
 collab-document = { version = "0.1.0" }
 collab-database = { version = "0.1.0" }
 collab-user = { version = "0.1.0" }
 collab-define = { version = "0.1.0" }
-flowy-user-deps = { path = "../flowy-user-deps" }
+flowy-user-deps = { workspace = true }
 anyhow = "1.0.75"
 
 tracing = { version = "0.1", features = ["log"] }
@@ -43,7 +43,7 @@ validator = "0.16.0"
 unicode-segmentation = "1.10"
 fancy-regex = "0.11.0"
 uuid = { version = "1.3.3", features = [ "v4"] }
-chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
+chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
 base64 = "^0.21"
 
 [dev-dependencies]

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

@@ -453,6 +453,7 @@ pub async fn reset_workspace_handler(
       "The workspace id is empty",
     ));
   }
-  manager.reset_workspace(reset_pb).await?;
+  let session = manager.get_session()?;
+  manager.reset_workspace(reset_pb, session.device_id).await?;
   Ok(())
 }

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

@@ -1,13 +1,13 @@
 use std::string::ToString;
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
-use appflowy_integrate::RocksCollabDB;
 use collab_user::core::MutexUserAwareness;
 use serde_json::Value;
 use tokio::sync::{Mutex, RwLock};
 use uuid::Uuid;
 
+use collab_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab_integrate::RocksCollabDB;
 use flowy_error::{internal_error, ErrorCode, FlowyResult};
 use flowy_sqlite::kv::StorePreferences;
 use flowy_sqlite::schema::user_table;
@@ -597,6 +597,7 @@ impl UserManager {
 
     if let Err(err) = sync_user_data_to_cloud(
       self.cloud_services.get_user_service()?,
+      "",
       new_user,
       &new_collab_db,
     )

+ 1 - 1
frontend/rust-lib/flowy-user/src/migrations/historical_document.rs

@@ -1,12 +1,12 @@
 use std::sync::Arc;
 
-use appflowy_integrate::{RocksCollabDB, YrsDocAction};
 use collab::core::collab::MutexCollab;
 use collab::core::origin::{CollabClient, CollabOrigin};
 use collab_document::document::Document;
 use collab_document::document_data::default_document_data;
 use collab_folder::core::Folder;
 
+use collab_integrate::{RocksCollabDB, YrsDocAction};
 use flowy_error::{internal_error, FlowyResult};
 
 use crate::migrations::migration::UserDataMigration;

+ 1 - 1
frontend/rust-lib/flowy-user/src/migrations/migrate_to_new_user.rs

@@ -3,7 +3,6 @@ use std::ops::{Deref, DerefMut};
 use std::sync::Arc;
 
 use anyhow::anyhow;
-use appflowy_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
 use collab::core::collab::MutexCollab;
 use collab::core::origin::{CollabClient, CollabOrigin};
 use collab::preclude::Collab;
@@ -15,6 +14,7 @@ use collab_database::user::DatabaseWithViewsArray;
 use collab_folder::core::Folder;
 use parking_lot::{Mutex, RwLock};
 
+use collab_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_folder_deps::cloud::gen_view_id;
 

+ 1 - 1
frontend/rust-lib/flowy-user/src/migrations/migration.rs

@@ -1,9 +1,9 @@
 use std::sync::Arc;
 
-use appflowy_integrate::RocksCollabDB;
 use chrono::NaiveDateTime;
 use diesel::{RunQueryDsl, SqliteConnection};
 
+use collab_integrate::RocksCollabDB;
 use flowy_error::FlowyResult;
 use flowy_sqlite::schema::user_data_migration_records;
 use flowy_sqlite::ConnectionPool;

+ 49 - 12
frontend/rust-lib/flowy-user/src/migrations/sync_new_user.rs

@@ -4,7 +4,6 @@ use std::pin::Pin;
 use std::sync::Arc;
 
 use anyhow::{anyhow, Error};
-use appflowy_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
 use collab::core::collab::MutexCollab;
 use collab::preclude::Collab;
 use collab_database::database::get_database_row_ids;
@@ -14,6 +13,7 @@ use collab_define::{CollabObject, CollabType};
 use collab_folder::core::{Folder, View, ViewLayout};
 use parking_lot::Mutex;
 
+use collab_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
 use flowy_error::FlowyResult;
 use flowy_user_deps::cloud::UserCloudService;
 
@@ -22,16 +22,27 @@ use crate::migrations::MigrationUser;
 #[tracing::instrument(level = "info", skip_all, err)]
 pub async fn sync_user_data_to_cloud(
   user_service: Arc<dyn UserCloudService>,
+  device_id: &str,
   new_user: &MigrationUser,
   collab_db: &Arc<RocksCollabDB>,
 ) -> FlowyResult<()> {
   let workspace_id = new_user.session.user_workspace.id.clone();
   let uid = new_user.session.user_id;
-  let folder = Arc::new(sync_folder(uid, &workspace_id, collab_db, user_service.clone()).await?);
+  let folder = Arc::new(
+    sync_folder(
+      uid,
+      &workspace_id,
+      device_id,
+      collab_db,
+      user_service.clone(),
+    )
+    .await?,
+  );
 
   let database_records = sync_database_views(
     uid,
     &workspace_id,
+    device_id,
     &new_user.session.user_workspace.database_views_aggregate_id,
     collab_db,
     user_service.clone(),
@@ -46,6 +57,7 @@ pub async fn sync_user_data_to_cloud(
       folder.clone(),
       database_records.clone(),
       workspace_id.to_string(),
+      device_id.to_string(),
       view,
       collab_db.clone(),
       user_service.clone(),
@@ -63,6 +75,7 @@ fn sync_views(
   folder: Arc<MutexFolder>,
   database_records: Vec<Arc<DatabaseWithViews>>,
   workspace_id: String,
+  device_id: String,
   view: Arc<View>,
   collab_db: Arc<RocksCollabDB>,
   user_service: Arc<dyn UserCloudService>,
@@ -77,8 +90,13 @@ fn sync_views(
       object_id
     );
 
-    let collab_object =
-      CollabObject::new(uid, object_id, collab_type).with_workspace_id(workspace_id.to_string());
+    let collab_object = CollabObject::new(
+      uid,
+      object_id,
+      collab_type,
+      workspace_id.to_string(),
+      device_id.clone(),
+    );
 
     match view.layout {
       ViewLayout::Document => {
@@ -108,8 +126,13 @@ fn sync_views(
           tracing::debug!("sync row: {}", row_id);
           let document_id = database_row_document_id_from_row_id(&row_id);
 
-          let database_row_collab_object = CollabObject::new(uid, row_id, CollabType::DatabaseRow)
-            .with_workspace_id(workspace_id.to_string());
+          let database_row_collab_object = CollabObject::new(
+            uid,
+            row_id,
+            CollabType::DatabaseRow,
+            workspace_id.to_string(),
+            device_id.clone(),
+          );
           let database_row_update =
             get_collab_init_update(uid, &database_row_collab_object, &collab_db)?;
           tracing::info!(
@@ -122,8 +145,13 @@ fn sync_views(
             .create_collab_object(&database_row_collab_object, database_row_update)
             .await;
 
-          let database_row_document = CollabObject::new(uid, document_id, CollabType::Document)
-            .with_workspace_id(workspace_id.to_string());
+          let database_row_document = CollabObject::new(
+            uid,
+            document_id,
+            CollabType::Document,
+            workspace_id.to_string(),
+            device_id.to_string(),
+          );
           // sync document in the row if exist
           if let Ok(document_update) =
             get_collab_init_update(uid, &database_row_document, &collab_db)
@@ -149,6 +177,7 @@ fn sync_views(
         folder.clone(),
         database_records.clone(),
         workspace_id.clone(),
+        device_id.to_string(),
         child_view,
         collab_db.clone(),
         user_service.clone(),
@@ -210,6 +239,7 @@ fn get_database_init_update(
 async fn sync_folder(
   uid: i64,
   workspace_id: &str,
+  device_id: &str,
   collab_db: &Arc<RocksCollabDB>,
   user_service: Arc<dyn UserCloudService>,
 ) -> Result<MutexFolder, Error> {
@@ -231,8 +261,13 @@ async fn sync_folder(
     )
   };
 
-  let collab_object = CollabObject::new(uid, workspace_id.to_string(), CollabType::Folder)
-    .with_workspace_id(workspace_id.to_string());
+  let collab_object = CollabObject::new(
+    uid,
+    workspace_id.to_string(),
+    CollabType::Folder,
+    workspace_id.to_string(),
+    device_id.to_string(),
+  );
   tracing::info!(
     "sync object: {} with update: {}",
     collab_object,
@@ -251,6 +286,7 @@ async fn sync_folder(
 async fn sync_database_views(
   uid: i64,
   workspace_id: &str,
+  device_id: &str,
   database_views_aggregate_id: &str,
   collab_db: &Arc<RocksCollabDB>,
   user_service: Arc<dyn UserCloudService>,
@@ -259,8 +295,9 @@ async fn sync_database_views(
     uid,
     database_views_aggregate_id.to_string(),
     CollabType::WorkspaceDatabase,
-  )
-  .with_workspace_id(workspace_id.to_string());
+    workspace_id.to_string(),
+    device_id.to_string(),
+  );
 
   // Use the temporary result to short the lifetime of the TransactionMut
   let result = {

+ 1 - 1
frontend/rust-lib/flowy-user/src/services/database.rs

@@ -1,10 +1,10 @@
 use std::path::PathBuf;
 use std::{collections::HashMap, sync::Arc, time::Duration};
 
-use appflowy_integrate::RocksCollabDB;
 use lazy_static::lazy_static;
 use parking_lot::RwLock;
 
+use collab_integrate::RocksCollabDB;
 use flowy_error::{ErrorCode, FlowyError};
 use flowy_sqlite::schema::user_workspace_table;
 use flowy_sqlite::ConnectionPool;

+ 1 - 1
frontend/rust-lib/flowy-user/src/services/user_awareness.rs

@@ -1,11 +1,11 @@
 use std::sync::{Arc, Weak};
 
-use appflowy_integrate::RocksCollabDB;
 use collab::core::collab::{CollabRawData, MutexCollab};
 use collab_define::reminder::Reminder;
 use collab_define::CollabType;
 use collab_user::core::{MutexUserAwareness, UserAwareness};
 
+use collab_integrate::RocksCollabDB;
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 
 use crate::entities::ReminderPB;

+ 12 - 4
frontend/rust-lib/flowy-user/src/services/user_workspace.rs

@@ -89,10 +89,18 @@ impl UserManager {
 
   /// Reset the remote workspace using local workspace data. This is useful when a user wishes to
   /// open a workspace on a new device that hasn't fully synchronized with the server.
-  pub async fn reset_workspace(&self, reset: ResetWorkspacePB) -> FlowyResult<()> {
-    let collab_object =
-      CollabObject::new(reset.uid, reset.workspace_id.clone(), CollabType::Folder)
-        .with_workspace_id(reset.workspace_id);
+  pub async fn reset_workspace(
+    &self,
+    reset: ResetWorkspacePB,
+    device_id: String,
+  ) -> FlowyResult<()> {
+    let collab_object = CollabObject::new(
+      reset.uid,
+      reset.workspace_id.clone(),
+      CollabType::Folder,
+      reset.workspace_id.clone(),
+      device_id,
+    );
     self
       .cloud_services
       .get_user_service()?

+ 30 - 0
frontend/scripts/tool/update_collab_rev.sh

@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# Ensure a new revision ID is provided
+if [ "$#" -ne 1 ]; then
+    echo "Usage: $0 <new_revision_id>"
+    exit 1
+fi
+
+NEW_REV="$1"
+echo "New revision: $NEW_REV"
+directories=("rust-lib" "appflowy_tauri/src-tauri")
+
+for dir in "${directories[@]}"; do
+    echo "Updating $dir"
+
+    cd "$dir"
+    sed -i.bak "/^collab[[:alnum:]-]*[[:space:]]*=/s/rev = \"[a-fA-F0-9]\{6,40\}\"/rev = \"$NEW_REV\"/g" Cargo.toml
+
+    # Detect changed crates
+    collab_crates=($(grep -E '^collab[a-zA-Z0-9_-]* =' Cargo.toml | awk -F'=' '{print $1}' | tr -d ' '))
+
+    # Update only the changed crates in Cargo.lock
+    for crate in "${collab_crates[@]}"; do
+        echo "Updating $crate"
+        cargo update -p $crate
+    done
+
+    cd ..
+done
+

+ 2 - 328
shared-lib/Cargo.lock

@@ -46,29 +46,12 @@ dependencies = [
  "syn 1.0.109",
 ]
 
-[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi 0.1.19",
- "libc",
- "winapi",
-]
-
 [[package]]
 name = "autocfg"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
-[[package]]
-name = "base64"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
-
 [[package]]
 name = "basic-toml"
 version = "0.1.2"
@@ -99,15 +82,6 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
-[[package]]
-name = "block-buffer"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "block-buffer"
 version = "0.10.4"
@@ -133,12 +107,6 @@ version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
 
-[[package]]
-name = "byteorder"
-version = "1.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
-
 [[package]]
 name = "bytes"
 version = "1.4.0"
@@ -327,22 +295,13 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690"
 
-[[package]]
-name = "digest"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
-dependencies = [
- "generic-array",
-]
-
 [[package]]
 name = "digest"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
 dependencies = [
- "block-buffer 0.10.4",
+ "block-buffer",
  "crypto-common",
 ]
 
@@ -358,19 +317,6 @@ version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
 
-[[package]]
-name = "env_logger"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
-dependencies = [
- "atty",
- "humantime",
- "log",
- "regex",
- "termcolor",
-]
-
 [[package]]
 name = "errno"
 version = "0.3.1"
@@ -479,104 +425,12 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
-[[package]]
-name = "form_urlencoded"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
-dependencies = [
- "percent-encoding",
-]
-
-[[package]]
-name = "futures"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-executor",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-channel"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
-dependencies = [
- "futures-core",
- "futures-sink",
-]
-
 [[package]]
 name = "futures-core"
 version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
 
-[[package]]
-name = "futures-executor"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
-dependencies = [
- "futures-core",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-io"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
-
-[[package]]
-name = "futures-macro"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "futures-sink"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
-
-[[package]]
-name = "futures-task"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
-
-[[package]]
-name = "futures-util"
-version = "0.3.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-macro",
- "futures-sink",
- "futures-task",
- "memchr",
- "pin-project-lite",
- "pin-utils",
- "slab",
-]
-
 [[package]]
 name = "generic-array"
 version = "0.14.4"
@@ -669,23 +523,6 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
 
-[[package]]
-name = "http"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
-dependencies = [
- "bytes",
- "fnv",
- "itoa",
-]
-
-[[package]]
-name = "httparse"
-version = "1.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
-
 [[package]]
 name = "humansize"
 version = "2.1.3"
@@ -695,12 +532,6 @@ dependencies = [
  "libm",
 ]
 
-[[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
 [[package]]
 name = "iana-time-zone"
 version = "0.1.53"
@@ -725,16 +556,6 @@ dependencies = [
  "cxx-build",
 ]
 
-[[package]]
-name = "idna"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
-dependencies = [
- "unicode-bidi",
- "unicode-normalization",
-]
-
 [[package]]
 name = "ignore"
 version = "0.4.20"
@@ -851,32 +672,6 @@ dependencies = [
  "tracing",
 ]
 
-[[package]]
-name = "lib-ws"
-version = "0.1.0"
-dependencies = [
- "bytes",
- "dashmap",
- "env_logger",
- "futures",
- "futures-channel",
- "futures-core",
- "futures-util",
- "lib-infra",
- "log",
- "parking_lot",
- "pin-project",
- "protobuf",
- "serde",
- "serde_json",
- "serde_repr",
- "strum_macros",
- "tokio",
- "tokio-tungstenite",
- "tracing",
- "url",
-]
-
 [[package]]
 name = "libc"
 version = "0.2.139"
@@ -981,12 +776,6 @@ version = "1.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 
-[[package]]
-name = "opaque-debug"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
-
 [[package]]
 name = "os_pipe"
 version = "0.9.2"
@@ -1188,12 +977,6 @@ version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
 
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
 [[package]]
 name = "ppv-lite86"
 version = "0.2.15"
@@ -1523,30 +1306,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "serde_repr"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "sha-1"
-version = "0.9.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
-dependencies = [
- "block-buffer 0.9.0",
- "cfg-if",
- "cpufeatures",
- "digest 0.9.0",
- "opaque-debug",
-]
-
 [[package]]
 name = "sha2"
 version = "0.10.6"
@@ -1555,7 +1314,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest 0.10.6",
+ "digest",
 ]
 
 [[package]]
@@ -1579,12 +1338,6 @@ version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
 
-[[package]]
-name = "slab"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
-
 [[package]]
 name = "slug"
 version = "0.1.4"
@@ -1734,21 +1487,6 @@ dependencies = [
  "once_cell",
 ]
 
-[[package]]
-name = "tinyvec"
-version = "1.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
-dependencies = [
- "tinyvec_macros",
-]
-
-[[package]]
-name = "tinyvec_macros"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
-
 [[package]]
 name = "tokio"
 version = "1.26.0"
@@ -1780,19 +1518,6 @@ dependencies = [
  "syn 1.0.109",
 ]
 
-[[package]]
-name = "tokio-tungstenite"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
-dependencies = [
- "futures-util",
- "log",
- "pin-project",
- "tokio",
- "tungstenite",
-]
-
 [[package]]
 name = "toml"
 version = "0.5.11"
@@ -1850,25 +1575,6 @@ dependencies = [
  "termcolor",
 ]
 
-[[package]]
-name = "tungstenite"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
-dependencies = [
- "base64",
- "byteorder",
- "bytes",
- "http",
- "httparse",
- "log",
- "rand 0.8.5",
- "sha-1",
- "thiserror",
- "url",
- "utf-8",
-]
-
 [[package]]
 name = "typenum"
 version = "1.14.0"
@@ -1940,27 +1646,12 @@ dependencies = [
  "unic-common",
 ]
 
-[[package]]
-name = "unicode-bidi"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
-
 [[package]]
 name = "unicode-ident"
 version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
 
-[[package]]
-name = "unicode-normalization"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
-dependencies = [
- "tinyvec",
-]
-
 [[package]]
 name = "unicode-segmentation"
 version = "1.10.1"
@@ -1973,23 +1664,6 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
-[[package]]
-name = "url"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
-dependencies = [
- "form_urlencoded",
- "idna",
- "percent-encoding",
-]
-
-[[package]]
-name = "utf-8"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
-
 [[package]]
 name = "version_check"
 version = "0.9.3"

+ 0 - 1
shared-lib/Cargo.toml

@@ -1,7 +1,6 @@
 [workspace]
 members = [
   "lib-ot",
-  "lib-ws",
   "lib-infra",
   "flowy-derive",
   "flowy-ast",

+ 1 - 1
shared-lib/lib-infra/Cargo.toml

@@ -6,7 +6,7 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
+chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
 bytes = { version = "1.4" }
 pin-project = "1.0.12"
 futures-core = { version = "0.3" }

+ 0 - 32
shared-lib/lib-ws/Cargo.toml

@@ -1,32 +0,0 @@
-[package]
-name = "lib-ws"
-version = "0.1.0"
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-serde_repr = "0.1"
-serde = "1.0"
-serde_json = {version = "1.0"}
-lib-infra = { path = "../lib-infra" }
-
-tokio-tungstenite = "0.15"
-futures-util = "0.3.26"
-futures-channel = "0.3.26"
-tokio = { version = "1.26", features = ["full"]}
-futures = "0.3.26"
-bytes = "1.4"
-pin-project = "1.0"
-futures-core = { version = "0.3", default-features = false }
-url = "2.3.1"
-log = "0.4"
-tracing = { version = "0.1", features = ["log"] }
-protobuf = {version = "2.28.0"}
-strum_macros = "0.21"
-parking_lot = "0.12.1"
-dashmap = "5"
-
-[dev-dependencies]
-tokio = { version = "1.26", features = ["full"]}
-env_logger = "0.8.4"

+ 0 - 223
shared-lib/lib-ws/src/connect.rs

@@ -1,223 +0,0 @@
-#![allow(clippy::all)]
-use crate::{
-  errors::{internal_error, WSError},
-  MsgReceiver, MsgSender,
-};
-use futures_core::{future::BoxFuture, ready};
-use futures_util::{FutureExt, StreamExt};
-use pin_project::pin_project;
-use std::{
-  fmt,
-  future::Future,
-  pin::Pin,
-  task::{Context, Poll},
-};
-use tokio::net::TcpStream;
-use tokio_tungstenite::{
-  connect_async,
-  tungstenite::{handshake::client::Response, Error, Message},
-  MaybeTlsStream, WebSocketStream,
-};
-
-type WsConnectResult = Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), Error>;
-
-#[pin_project]
-pub struct WSConnectionFuture {
-  msg_tx: Option<MsgSender>,
-  ws_rx: Option<MsgReceiver>,
-  #[pin]
-  fut: Pin<Box<dyn Future<Output = WsConnectResult> + Send + Sync>>,
-}
-
-impl WSConnectionFuture {
-  pub fn new(msg_tx: MsgSender, ws_rx: MsgReceiver, addr: String) -> Self {
-    WSConnectionFuture {
-      msg_tx: Some(msg_tx),
-      ws_rx: Some(ws_rx),
-      fut: Box::pin(async move { connect_async(&addr).await }),
-    }
-  }
-}
-
-impl Future for WSConnectionFuture {
-  type Output = Result<WSStream, WSError>;
-  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-    // [[pin]]
-    // poll async function.  The following methods not work.
-    // 1.
-    // let f = connect_async("");
-    // pin_mut!(f);
-    // ready!(Pin::new(&mut a).poll(cx))
-    //
-    // 2.ready!(Pin::new(&mut Box::pin(connect_async(""))).poll(cx))
-    //
-    // An async method calls poll multiple times and might return to the executor. A
-    // single poll call can only return to the executor once and will get
-    // resumed through another poll invocation. the connect_async call multiple time
-    // from the beginning. So I use fut to hold the future and continue to
-    // poll it. (Fix me if i was wrong)
-    loop {
-      return match ready!(self.as_mut().project().fut.poll(cx)) {
-        Ok((stream, _)) => {
-          tracing::debug!("[WebSocket]: connect success");
-          let (msg_tx, ws_rx) = (
-            self
-              .msg_tx
-              .take()
-              .expect("[WebSocket]: WSConnection should be call once "),
-            self
-              .ws_rx
-              .take()
-              .expect("[WebSocket]: WSConnection should be call once "),
-          );
-          Poll::Ready(Ok(WSStream::new(msg_tx, ws_rx, stream)))
-        },
-        Err(error) => {
-          tracing::debug!("[WebSocket]: ❌ connect failed: {:?}", error);
-          Poll::Ready(Err(error.into()))
-        },
-      };
-    }
-  }
-}
-
-type Fut = BoxFuture<'static, Result<(), WSError>>;
-#[pin_project]
-pub struct WSStream {
-  #[allow(dead_code)]
-  msg_tx: MsgSender,
-  #[pin]
-  inner: Option<(Fut, Fut)>,
-}
-
-impl WSStream {
-  pub fn new(
-    msg_tx: MsgSender,
-    ws_rx: MsgReceiver,
-    stream: WebSocketStream<MaybeTlsStream<TcpStream>>,
-  ) -> Self {
-    let (ws_write, ws_read) = stream.split();
-    Self {
-      msg_tx: msg_tx.clone(),
-      inner: Some((
-        Box::pin(async move {
-          let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
-          let read = async {
-            ws_read
-              .for_each(|message| async {
-                match tx.send(send_message(msg_tx.clone(), message)) {
-                  Ok(_) => {},
-                  Err(e) => log::error!("[WebSocket]: WSStream sender closed unexpectedly: {} ", e),
-                }
-              })
-              .await;
-            Ok(())
-          };
-
-          let read_ret = async {
-            loop {
-              match rx.recv().await {
-                None => {
-                  return Err(
-                    WSError::internal()
-                      .context("[WebSocket]: WSStream receiver closed unexpectedly"),
-                  );
-                },
-                Some(result) => {
-                  if result.is_err() {
-                    return result;
-                  }
-                },
-              }
-            }
-          };
-          futures::pin_mut!(read);
-          futures::pin_mut!(read_ret);
-          return tokio::select! {
-              result = read => result,
-              result = read_ret => result,
-          };
-        }),
-        Box::pin(async move {
-          let result = ws_rx
-            .map(Ok)
-            .forward(ws_write)
-            .await
-            .map_err(internal_error);
-          result
-        }),
-      )),
-    }
-  }
-}
-
-impl fmt::Debug for WSStream {
-  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-    f.debug_struct("WSStream").finish()
-  }
-}
-
-impl Future for WSStream {
-  type Output = Result<(), WSError>;
-
-  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-    let (mut ws_read, mut ws_write) = self.inner.take().unwrap();
-    match ws_read.poll_unpin(cx) {
-      Poll::Ready(l) => Poll::Ready(l),
-      Poll::Pending => {
-        //
-        match ws_write.poll_unpin(cx) {
-          Poll::Ready(r) => Poll::Ready(r),
-          Poll::Pending => {
-            self.inner = Some((ws_read, ws_write));
-            Poll::Pending
-          },
-        }
-      },
-    }
-  }
-}
-
-fn send_message(msg_tx: MsgSender, message: Result<Message, Error>) -> Result<(), WSError> {
-  match message {
-    Ok(Message::Binary(bytes)) => msg_tx
-      .unbounded_send(Message::Binary(bytes))
-      .map_err(internal_error),
-    Ok(_) => Ok(()),
-    Err(e) => Err(WSError::internal().context(e)),
-  }
-}
-#[allow(dead_code)]
-pub struct Retry<F> {
-  f: F,
-  #[allow(dead_code)]
-  retry_time: usize,
-  addr: String,
-}
-
-impl<F> Retry<F>
-where
-  F: Fn(&str),
-{
-  #[allow(dead_code)]
-  pub fn new(addr: &str, f: F) -> Self {
-    Self {
-      f,
-      retry_time: 3,
-      addr: addr.to_owned(),
-    }
-  }
-}
-
-impl<F> Future for Retry<F>
-where
-  F: Fn(&str),
-{
-  type Output = ();
-
-  fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
-    (self.f)(&self.addr);
-
-    Poll::Ready(())
-  }
-}

+ 0 - 101
shared-lib/lib-ws/src/errors.rs

@@ -1,101 +0,0 @@
-use futures_channel::mpsc::TrySendError;
-use serde::{Deserialize, Serialize};
-use serde_repr::*;
-use std::fmt::Debug;
-use strum_macros::Display;
-use tokio::sync::oneshot::error::RecvError;
-use tokio_tungstenite::tungstenite::{http::StatusCode, Message};
-use url::ParseError;
-
-#[derive(Debug, Default, Clone, Serialize, Deserialize)]
-pub struct WSError {
-  pub code: ErrorCode,
-  pub msg: String,
-}
-
-macro_rules! static_ws_error {
-  ($name:ident, $status:expr) => {
-    #[allow(non_snake_case, missing_docs)]
-    pub fn $name() -> WSError {
-      WSError {
-        code: $status,
-        msg: format!("{}", $status),
-      }
-    }
-  };
-}
-
-impl WSError {
-  #[allow(dead_code)]
-  pub(crate) fn new(code: ErrorCode) -> WSError {
-    WSError {
-      code,
-      msg: "".to_string(),
-    }
-  }
-
-  pub fn context<T: Debug>(mut self, error: T) -> Self {
-    self.msg = format!("{:?}", error);
-    self
-  }
-
-  static_ws_error!(internal, ErrorCode::InternalError);
-  static_ws_error!(unsupported_message, ErrorCode::UnsupportedMessage);
-  static_ws_error!(unauthorized, ErrorCode::Unauthorized);
-}
-
-pub(crate) fn internal_error<T>(e: T) -> WSError
-where
-  T: std::fmt::Debug,
-{
-  WSError::internal().context(e)
-}
-
-#[derive(Debug, Clone, Serialize_repr, Deserialize_repr, Display, PartialEq, Eq)]
-#[repr(u8)]
-#[derive(Default)]
-pub enum ErrorCode {
-  #[default]
-  InternalError = 0,
-  UnsupportedMessage = 1,
-  Unauthorized = 2,
-}
-
-impl std::convert::From<url::ParseError> for WSError {
-  fn from(error: ParseError) -> Self {
-    WSError::internal().context(error)
-  }
-}
-
-impl std::convert::From<protobuf::ProtobufError> for WSError {
-  fn from(error: protobuf::ProtobufError) -> Self {
-    WSError::internal().context(error)
-  }
-}
-
-impl std::convert::From<futures_channel::mpsc::TrySendError<Message>> for WSError {
-  fn from(error: TrySendError<Message>) -> Self {
-    WSError::internal().context(error)
-  }
-}
-
-impl std::convert::From<RecvError> for WSError {
-  fn from(error: RecvError) -> Self {
-    WSError::internal().context(error)
-  }
-}
-
-impl std::convert::From<tokio_tungstenite::tungstenite::Error> for WSError {
-  fn from(error: tokio_tungstenite::tungstenite::Error) -> Self {
-    match error {
-      tokio_tungstenite::tungstenite::Error::Http(response) => {
-        if response.status() == StatusCode::UNAUTHORIZED {
-          WSError::unauthorized()
-        } else {
-          WSError::internal().context(response)
-        }
-      },
-      _ => WSError::internal().context(error),
-    }
-  }
-}

+ 0 - 7
shared-lib/lib-ws/src/lib.rs

@@ -1,7 +0,0 @@
-pub mod connect;
-pub mod errors;
-mod msg;
-mod ws;
-
-pub use msg::*;
-pub use ws::*;

+ 0 - 46
shared-lib/lib-ws/src/msg.rs

@@ -1,46 +0,0 @@
-use serde::{Deserialize, Serialize};
-use serde_repr::*;
-use tokio_tungstenite::tungstenite::Message as TokioMessage;
-
-#[derive(Serialize, Deserialize, Debug, Clone, Default)]
-pub struct WebSocketRawMessage {
-  pub channel: WSChannel,
-  pub data: Vec<u8>,
-}
-
-impl WebSocketRawMessage {
-  pub fn to_bytes(&self) -> Vec<u8> {
-    serde_json::to_vec(&self).unwrap_or_default()
-  }
-
-  pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Self {
-    serde_json::from_slice(bytes.as_ref()).unwrap_or_default()
-  }
-}
-
-// The lib-ws crate should not contain business logic.So WSChannel should be removed into another place.
-#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Eq, PartialEq, Hash)]
-#[repr(u8)]
-#[derive(Default)]
-pub enum WSChannel {
-  #[default]
-  Document = 0,
-  Folder = 1,
-  Database = 2,
-}
-
-impl ToString for WSChannel {
-  fn to_string(&self) -> String {
-    match self {
-      WSChannel::Document => "0".to_string(),
-      WSChannel::Folder => "1".to_string(),
-      WSChannel::Database => "2".to_string(),
-    }
-  }
-}
-
-impl std::convert::From<WebSocketRawMessage> for TokioMessage {
-  fn from(msg: WebSocketRawMessage) -> Self {
-    TokioMessage::Binary(msg.to_bytes())
-  }
-}

+ 0 - 435
shared-lib/lib-ws/src/ws.rs

@@ -1,435 +0,0 @@
-#![allow(clippy::type_complexity)]
-use crate::{
-  connect::{WSConnectionFuture, WSStream},
-  errors::WSError,
-  WSChannel, WebSocketRawMessage,
-};
-use dashmap::DashMap;
-use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
-use futures_core::{ready, Stream};
-use lib_infra::retry::{Action, FixedInterval, Retry};
-use pin_project::pin_project;
-use std::{
-  fmt::Formatter,
-  future::Future,
-  pin::Pin,
-  sync::Arc,
-  task::{Context, Poll},
-  time::Duration,
-};
-use tokio::sync::{broadcast, oneshot, RwLock};
-use tokio_tungstenite::tungstenite::{
-  protocol::{frame::coding::CloseCode, CloseFrame},
-  Message,
-};
-
-pub type MsgReceiver = UnboundedReceiver<Message>;
-pub type MsgSender = UnboundedSender<Message>;
-type Handlers = DashMap<WSChannel, Arc<dyn WSMessageReceiver>>;
-
-pub trait WSMessageReceiver: Sync + Send + 'static {
-  fn source(&self) -> WSChannel;
-  fn receive_message(&self, msg: WebSocketRawMessage);
-}
-
-pub struct WSController {
-  handlers: Handlers,
-  addr: Arc<RwLock<Option<String>>>,
-  sender: Arc<RwLock<Option<Arc<WSSender>>>>,
-  conn_state_notify: Arc<RwLock<WSConnectStateNotifier>>,
-}
-
-impl std::fmt::Display for WSController {
-  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-    f.write_str("WebSocket")
-  }
-}
-
-impl std::default::Default for WSController {
-  fn default() -> Self {
-    Self {
-      handlers: DashMap::new(),
-      addr: Arc::new(RwLock::new(None)),
-      sender: Arc::new(RwLock::new(None)),
-      conn_state_notify: Arc::new(RwLock::new(WSConnectStateNotifier::default())),
-    }
-  }
-}
-
-impl WSController {
-  pub fn new() -> Self {
-    WSController::default()
-  }
-
-  pub fn add_ws_message_receiver(
-    &self,
-    handler: Arc<dyn WSMessageReceiver>,
-  ) -> Result<(), WSError> {
-    let source = handler.source();
-    if self.handlers.contains_key(&source) {
-      log::error!("{:?} is already registered", source);
-    }
-    self.handlers.insert(source, handler);
-    Ok(())
-  }
-
-  pub async fn start(&self, addr: String) -> Result<(), WSError> {
-    *self.addr.write().await = Some(addr.clone());
-    let strategy = FixedInterval::from_millis(5000).take(3);
-    self.connect(addr, strategy).await
-  }
-
-  pub async fn stop(&self) {
-    if self
-      .conn_state_notify
-      .read()
-      .await
-      .conn_state
-      .is_connected()
-    {
-      tracing::trace!("[{}] stop", self);
-      self
-        .conn_state_notify
-        .write()
-        .await
-        .update_state(WSConnectState::Disconnected);
-    }
-  }
-
-  async fn connect<T, I>(&self, addr: String, strategy: T) -> Result<(), WSError>
-  where
-    T: IntoIterator<IntoIter = I, Item = Duration>,
-    I: Iterator<Item = Duration> + Send + 'static,
-  {
-    let mut conn_state_notify = self.conn_state_notify.write().await;
-    let conn_state = conn_state_notify.conn_state.clone();
-    if conn_state.is_connected() || conn_state.is_connecting() {
-      return Ok(());
-    }
-
-    let (ret, rx) = oneshot::channel::<Result<(), WSError>>();
-    *self.addr.write().await = Some(addr.clone());
-    let action = WSConnectAction {
-      addr,
-      handlers: self.handlers.clone(),
-    };
-    let retry = Retry::new(strategy, action);
-    conn_state_notify.update_state(WSConnectState::Connecting);
-    drop(conn_state_notify);
-
-    let cloned_conn_state = self.conn_state_notify.clone();
-    let cloned_sender = self.sender.clone();
-    tracing::trace!("[{}] start connecting", self);
-    tokio::spawn(async move {
-      match retry.await {
-        Ok(result) => {
-          let WSConnectResult {
-            stream,
-            handlers_fut,
-            sender,
-          } = result;
-
-          cloned_conn_state
-            .write()
-            .await
-            .update_state(WSConnectState::Connected);
-          *cloned_sender.write().await = Some(Arc::new(sender));
-
-          let _ = ret.send(Ok(()));
-          spawn_stream_and_handlers(stream, handlers_fut).await;
-        },
-        Err(e) => {
-          cloned_conn_state
-            .write()
-            .await
-            .update_state(WSConnectState::Disconnected);
-          let _ = ret.send(Err(WSError::internal().context(e)));
-        },
-      }
-    });
-    rx.await?
-  }
-
-  pub async fn retry(&self, count: usize) -> Result<(), WSError> {
-    if !self
-      .conn_state_notify
-      .read()
-      .await
-      .conn_state
-      .is_disconnected()
-    {
-      return Ok(());
-    }
-
-    tracing::trace!("[WebSocket]: retry connect...");
-    let strategy = FixedInterval::from_millis(5000).take(count);
-    let addr = self
-      .addr
-      .read()
-      .await
-      .as_ref()
-      .expect("Retry web socket connection failed, should call start_connect first")
-      .clone();
-
-    self.connect(addr, strategy).await
-  }
-
-  pub async fn subscribe_state(&self) -> broadcast::Receiver<WSConnectState> {
-    self.conn_state_notify.read().await.notify.subscribe()
-  }
-
-  pub async fn ws_message_sender(&self) -> Result<Option<Arc<WSSender>>, WSError> {
-    let sender = self.sender.read().await.clone();
-    match sender {
-      None => match self.conn_state_notify.read().await.conn_state {
-        WSConnectState::Disconnected => {
-          let msg = "WebSocket is disconnected";
-          Err(WSError::internal().context(msg))
-        },
-        _ => Ok(None),
-      },
-      Some(sender) => Ok(Some(sender)),
-    }
-  }
-}
-
-async fn spawn_stream_and_handlers(stream: WSStream, handlers: WSHandlerFuture) {
-  tokio::select! {
-      result = stream => {
-          if let Err(e) = result {
-              tracing::error!("WSStream error: {:?}", e);
-          }
-      },
-      result = handlers => tracing::debug!("handlers completed {:?}", result),
-  };
-}
-
-#[pin_project]
-pub struct WSHandlerFuture {
-  #[pin]
-  msg_rx: MsgReceiver,
-  handlers: Handlers,
-}
-
-impl WSHandlerFuture {
-  fn new(handlers: Handlers, msg_rx: MsgReceiver) -> Self {
-    Self { msg_rx, handlers }
-  }
-
-  fn handler_ws_message(&self, message: Message) {
-    if let Message::Binary(bytes) = message {
-      self.handle_binary_message(bytes)
-    }
-  }
-
-  fn handle_binary_message(&self, bytes: Vec<u8>) {
-    let msg = WebSocketRawMessage::from_bytes(bytes);
-    match self.handlers.get(&msg.channel) {
-      None => log::error!("Can't find any handler for message: {:?}", msg),
-      Some(handler) => handler.receive_message(msg),
-    }
-  }
-}
-
-impl Future for WSHandlerFuture {
-  type Output = ();
-  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-    loop {
-      match ready!(self.as_mut().project().msg_rx.poll_next(cx)) {
-        None => {
-          return Poll::Ready(());
-        },
-        Some(message) => self.handler_ws_message(message),
-      }
-    }
-  }
-}
-
-#[derive(Debug, Clone)]
-pub struct WSSender(MsgSender);
-
-impl WSSender {
-  pub fn send_msg<T: Into<WebSocketRawMessage>>(&self, msg: T) -> Result<(), WSError> {
-    let msg = msg.into();
-    self
-      .0
-      .unbounded_send(msg.into())
-      .map_err(|e| WSError::internal().context(e))?;
-    Ok(())
-  }
-
-  pub fn send_text(&self, source: &WSChannel, text: &str) -> Result<(), WSError> {
-    let msg = WebSocketRawMessage {
-      channel: source.clone(),
-      data: text.as_bytes().to_vec(),
-    };
-    self.send_msg(msg)
-  }
-
-  pub fn send_binary(&self, source: &WSChannel, bytes: Vec<u8>) -> Result<(), WSError> {
-    let msg = WebSocketRawMessage {
-      channel: source.clone(),
-      data: bytes,
-    };
-    self.send_msg(msg)
-  }
-
-  pub fn send_disconnect(&self, reason: &str) -> Result<(), WSError> {
-    let frame = CloseFrame {
-      code: CloseCode::Normal,
-      reason: reason.to_owned().into(),
-    };
-    let msg = Message::Close(Some(frame));
-    self
-      .0
-      .unbounded_send(msg)
-      .map_err(|e| WSError::internal().context(e))?;
-    Ok(())
-  }
-}
-
-struct WSConnectAction {
-  addr: String,
-  handlers: Handlers,
-}
-
-impl Action for WSConnectAction {
-  type Future = Pin<Box<dyn Future<Output = Result<Self::Item, Self::Error>> + Send + Sync>>;
-  type Item = WSConnectResult;
-  type Error = WSError;
-
-  fn run(&mut self) -> Self::Future {
-    let addr = self.addr.clone();
-    let handlers = self.handlers.clone();
-    Box::pin(WSConnectActionFut::new(addr, handlers))
-  }
-}
-
-struct WSConnectResult {
-  stream: WSStream,
-  handlers_fut: WSHandlerFuture,
-  sender: WSSender,
-}
-
-#[pin_project]
-struct WSConnectActionFut {
-  addr: String,
-  #[pin]
-  conn: WSConnectionFuture,
-  handlers_fut: Option<WSHandlerFuture>,
-  sender: Option<WSSender>,
-}
-
-impl WSConnectActionFut {
-  fn new(addr: String, handlers: Handlers) -> Self {
-    //                Stream                             User
-    //               ┌───────────────┐                 ┌──────────────┐
-    // ┌──────┐      │  ┌─────────┐  │    ┌────────┐   │  ┌────────┐  │
-    // │Server│──────┼─▶│ ws_read │──┼───▶│ msg_tx │───┼─▶│ msg_rx │  │
-    // └──────┘      │  └─────────┘  │    └────────┘   │  └────────┘  │
-    //     ▲         │               │                 │              │
-    //     │         │  ┌─────────┐  │    ┌────────┐   │  ┌────────┐  │
-    //     └─────────┼──│ws_write │◀─┼────│ ws_rx  │◀──┼──│ ws_tx  │  │
-    //               │  └─────────┘  │    └────────┘   │  └────────┘  │
-    //               └───────────────┘                 └──────────────┘
-    let (msg_tx, msg_rx) = futures_channel::mpsc::unbounded();
-    let (ws_tx, ws_rx) = futures_channel::mpsc::unbounded();
-    let sender = WSSender(ws_tx);
-    let handlers_fut = WSHandlerFuture::new(handlers, msg_rx);
-    let conn = WSConnectionFuture::new(msg_tx, ws_rx, addr.clone());
-    Self {
-      addr,
-      conn,
-      handlers_fut: Some(handlers_fut),
-      sender: Some(sender),
-    }
-  }
-}
-
-impl Future for WSConnectActionFut {
-  type Output = Result<WSConnectResult, WSError>;
-  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-    let mut this = self.project();
-    match ready!(this.conn.as_mut().poll(cx)) {
-      Ok(stream) => {
-        let handlers_fut = this.handlers_fut.take().expect("Only take once");
-        let sender = this.sender.take().expect("Only take once");
-        Poll::Ready(Ok(WSConnectResult {
-          stream,
-          handlers_fut,
-          sender,
-        }))
-      },
-      Err(e) => Poll::Ready(Err(e)),
-    }
-  }
-}
-
-#[derive(Clone, Eq, PartialEq)]
-pub enum WSConnectState {
-  Init,
-  Connecting,
-  Connected,
-  Disconnected,
-}
-
-impl WSConnectState {
-  fn is_connected(&self) -> bool {
-    self == &WSConnectState::Connected
-  }
-
-  fn is_connecting(&self) -> bool {
-    self == &WSConnectState::Connecting
-  }
-
-  fn is_disconnected(&self) -> bool {
-    self == &WSConnectState::Disconnected || self == &WSConnectState::Init
-  }
-}
-
-impl std::fmt::Display for WSConnectState {
-  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-    match self {
-      WSConnectState::Init => f.write_str("Init"),
-      WSConnectState::Connected => f.write_str("Connected"),
-      WSConnectState::Connecting => f.write_str("Connecting"),
-      WSConnectState::Disconnected => f.write_str("Disconnected"),
-    }
-  }
-}
-
-impl std::fmt::Debug for WSConnectState {
-  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-    f.write_str(&format!("{}", self))
-  }
-}
-
-struct WSConnectStateNotifier {
-  conn_state: WSConnectState,
-  notify: Arc<broadcast::Sender<WSConnectState>>,
-}
-
-impl std::default::Default for WSConnectStateNotifier {
-  fn default() -> Self {
-    let (state_notify, _) = broadcast::channel(16);
-    Self {
-      conn_state: WSConnectState::Init,
-      notify: Arc::new(state_notify),
-    }
-  }
-}
-
-impl WSConnectStateNotifier {
-  fn update_state(&mut self, new_state: WSConnectState) {
-    if self.conn_state == new_state {
-      return;
-    }
-    tracing::debug!(
-      "WebSocket connect state did change: {} -> {}",
-      self.conn_state,
-      new_state
-    );
-    self.conn_state = new_state.clone();
-    let _ = self.notify.send(new_state);
-  }
-}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików