Преглед изворни кода

add server with flowy_document test

appflowy пре 3 година
родитељ
комит
01c9620e03
43 измењених фајлова са 515 додато и 340 уклоњено
  1. 5 1
      backend/Cargo.toml
  2. 0 1
      backend/src/service/doc/edit_doc_context.rs
  3. 1 2
      backend/src/service/doc/ws_handler.rs
  4. 1 1
      backend/src/service/log/mod.rs
  5. 2 2
      backend/src/service/user/user_default.rs
  6. 1 0
      backend/src/service/ws/router.rs
  7. 1 1
      backend/src/service/ws/ws_client.rs
  8. 12 12
      backend/tests/api/auth.rs
  9. 2 7
      backend/tests/api/workspace.rs
  10. 97 0
      backend/tests/document/helper.rs
  11. 0 0
      backend/tests/document/mod.rs
  12. 12 0
      backend/tests/document/ws.rs
  13. 55 83
      backend/tests/helper.rs
  14. 1 1
      backend/tests/main.rs
  15. 0 76
      backend/tests/ws/helper.rs
  16. 0 13
      backend/tests/ws/ws.rs
  17. 0 0
      flowy-test/temp/flowy/flowy.log.2021-09-27
  18. 0 1
      rust-lib/Cargo.toml
  19. 10 2
      rust-lib/dart-ffi/src/lib.rs
  20. 12 4
      rust-lib/flowy-document/src/module.rs
  21. 3 8
      rust-lib/flowy-document/src/services/doc/doc_controller.rs
  22. 3 3
      rust-lib/flowy-document/src/services/doc/edit_doc_context.rs
  23. 4 7
      rust-lib/flowy-document/src/services/doc/rev_manager.rs
  24. 3 6
      rust-lib/flowy-document/src/sql_tables/doc/doc_op_sql.rs
  25. 0 8
      rust-lib/flowy-editor/Cargo.toml
  26. 0 7
      rust-lib/flowy-editor/src/lib.rs
  27. 38 0
      rust-lib/flowy-net/src/config.rs
  28. 1 0
      rust-lib/flowy-sdk/Cargo.toml
  29. 24 10
      rust-lib/flowy-sdk/src/lib.rs
  30. 19 16
      rust-lib/flowy-sdk/src/module.rs
  31. 11 2
      rust-lib/flowy-test/src/lib.rs
  32. 12 3
      rust-lib/flowy-user/src/errors.rs
  33. 6 3
      rust-lib/flowy-user/src/services/server/mod.rs
  34. 31 10
      rust-lib/flowy-user/src/services/server/server_api.rs
  35. 5 1
      rust-lib/flowy-user/src/services/server/server_api_mock.rs
  36. 3 2
      rust-lib/flowy-user/src/services/user/builder.rs
  37. 22 8
      rust-lib/flowy-user/src/services/user/user_session.rs
  38. 3 3
      rust-lib/flowy-workspace/src/entities/view/view_create.rs
  39. 14 3
      rust-lib/flowy-workspace/src/module.rs
  40. 8 3
      rust-lib/flowy-workspace/src/services/server/mod.rs
  41. 64 20
      rust-lib/flowy-workspace/src/services/server/server_api.rs
  42. 9 4
      rust-lib/flowy-workspace/src/services/view_controller.rs
  43. 20 6
      rust-lib/flowy-ws/src/ws.rs

+ 5 - 1
backend/Cargo.toml

@@ -95,4 +95,8 @@ once_cell = "1.7.2"
 linkify = "0.5.0"
 flowy-user = { path = "../rust-lib/flowy-user" }
 flowy-workspace = { path = "../rust-lib/flowy-workspace" }
-flowy-ws = { path = "../rust-lib/flowy-ws" }
+flowy-ws = { path = "../rust-lib/flowy-ws" }
+flowy-sdk = { path = "../rust-lib/flowy-sdk" }
+flowy-test = { path = "../rust-lib/flowy-test" }
+flowy-infra = { path = "../rust-lib/flowy-infra" }
+flowy-ot = { path = "../rust-lib/flowy-ot" }

+ 0 - 1
backend/src/service/doc/edit_doc_context.rs

@@ -22,7 +22,6 @@ use parking_lot::RwLock;
 use protobuf::Message;
 use sqlx::PgPool;
 use std::{
-    cmp::min,
     convert::TryInto,
     sync::{
         atomic::{AtomicI64, Ordering::SeqCst},

+ 1 - 2
backend/src/service/doc/ws_handler.rs

@@ -6,8 +6,7 @@ use crate::service::{
 };
 use actix_web::web::Data;
 
-use crate::service::ws::WsUser;
-use flowy_document::protobuf::{QueryDocParams, Revision, RevisionRange, WsDataType, WsDocumentData};
+use flowy_document::protobuf::{QueryDocParams, Revision, WsDataType, WsDocumentData};
 use flowy_net::errors::ServerError;
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
 use protobuf::Message;

+ 1 - 1
backend/src/service/log/mod.rs

@@ -28,7 +28,7 @@ impl Builder {
         let env_filter = EnvFilter::new(self.env_filter);
         let subscriber = tracing_subscriber::fmt()
             .with_target(true)
-            .with_max_level(tracing::Level::TRACE)
+            .with_max_level(tracing::Level::DEBUG)
             .with_writer(std::io::stderr)
             .with_thread_ids(false)
             .compact()

+ 2 - 2
backend/src/service/user/user_default.rs

@@ -8,7 +8,7 @@ use crate::{
 };
 use flowy_net::errors::ServerError;
 use flowy_workspace::{
-    entities::view::VIEW_DEFAULT_DATA,
+    entities::view::DOC_DEFAULT_DATA,
     protobuf::{App, CreateViewParams, View, ViewType, Workspace},
 };
 
@@ -62,7 +62,7 @@ async fn create_view(transaction: &mut DBTransaction<'_>, app: &App) -> Result<V
         desc: "View created by AppFlowy".to_string(),
         thumbnail: "123.png".to_string(),
         view_type: ViewType::Doc,
-        data: VIEW_DEFAULT_DATA.to_string(),
+        data: DOC_DEFAULT_DATA.to_string(),
         unknown_fields: Default::default(),
         cached_size: Default::default(),
     };

+ 1 - 0
backend/src/service/ws/router.rs

@@ -19,6 +19,7 @@ pub async fn establish_ws_connection(
     server: Data<Addr<WsServer>>,
     biz_handlers: Data<WsBizHandlers>,
 ) -> Result<HttpResponse, Error> {
+    log::debug!("establish_ws_connection");
     match LoggedUser::from_token(token.clone()) {
         Ok(user) => {
             let ws_user = WsUser::new(user.clone());

+ 1 - 1
backend/src/service/ws/ws_client.rs

@@ -3,7 +3,7 @@ use crate::{
     service::{
         user::LoggedUser,
         ws::{
-            entities::{Connect, Disconnect, SessionId, Socket},
+            entities::{Connect, Disconnect, Socket},
             WsBizHandler,
             WsBizHandlers,
             WsMessageAdaptor,

+ 12 - 12
backend/tests/api/auth.rs

@@ -1,9 +1,9 @@
-use crate::helper::{spawn_server, TestServer};
+use crate::helper::{spawn_user_server, TestUserServer};
 use flowy_user::entities::{SignInParams, SignUpParams, SignUpResponse, UpdateUserParams};
 
 #[actix_rt::test]
 async fn user_register() {
-    let app = spawn_server().await;
+    let app = spawn_user_server().await;
     let response = register_user(&app, "[email protected]", "HelloWorld123!").await;
     log::info!("{:?}", response);
 }
@@ -11,7 +11,7 @@ async fn user_register() {
 #[actix_rt::test]
 #[should_panic]
 async fn user_sign_in_with_invalid_password() {
-    let app = spawn_server().await;
+    let app = spawn_user_server().await;
     let email = "[email protected]";
     let password = "123";
     let _ = register_user(&app, email, password).await;
@@ -20,7 +20,7 @@ async fn user_sign_in_with_invalid_password() {
 #[actix_rt::test]
 #[should_panic]
 async fn user_sign_in_with_invalid_email() {
-    let app = spawn_server().await;
+    let app = spawn_user_server().await;
     let email = "annie@gmail@";
     let password = "HelloWorld123!";
     let _ = register_user(&app, email, password).await;
@@ -28,7 +28,7 @@ async fn user_sign_in_with_invalid_email() {
 
 #[actix_rt::test]
 async fn user_sign_in() {
-    let app = spawn_server().await;
+    let app = spawn_user_server().await;
     let email = "[email protected]";
     let password = "HelloWorld123!";
     let _ = register_user(&app, email, password).await;
@@ -42,7 +42,7 @@ async fn user_sign_in() {
 #[actix_rt::test]
 #[should_panic]
 async fn user_sign_out() {
-    let server = TestServer::new().await;
+    let server = TestUserServer::new().await;
     server.sign_out().await;
 
     // user_detail will be empty because use was sign out.
@@ -51,13 +51,13 @@ async fn user_sign_out() {
 
 #[actix_rt::test]
 async fn user_get_detail() {
-    let server = TestServer::new().await;
+    let server = TestUserServer::new().await;
     log::info!("{:?}", server.get_user_profile().await);
 }
 
 #[actix_rt::test]
 async fn user_update_password() {
-    let mut server = spawn_server().await;
+    let mut server = spawn_user_server().await;
     let email = "[email protected]";
     let password = "HelloWorld123!";
     let sign_up_resp = register_user(&server, email, password).await;
@@ -82,7 +82,7 @@ async fn user_update_password() {
 
 #[actix_rt::test]
 async fn user_update_name() {
-    let server = TestServer::new().await;
+    let server = TestUserServer::new().await;
 
     let name = "tom".to_string();
     let params = UpdateUserParams::new(&server.user_id()).name(&name);
@@ -94,7 +94,7 @@ async fn user_update_name() {
 
 #[actix_rt::test]
 async fn user_update_email() {
-    let server = TestServer::new().await;
+    let server = TestUserServer::new().await;
     let email = "[email protected]".to_string();
     let params = UpdateUserParams::new(server.user_id()).email(&email);
     server.update_user_profile(params).await.unwrap();
@@ -104,14 +104,14 @@ async fn user_update_email() {
 }
 
 #[allow(dead_code)]
-async fn sign_up_user(server: &TestServer) -> SignUpResponse {
+async fn sign_up_user(server: &TestUserServer) -> SignUpResponse {
     let email = "[email protected]";
     let password = "HelloWorld123!";
     let response = register_user(server, email, password).await;
     response
 }
 
-async fn register_user(server: &TestServer, email: &str, password: &str) -> SignUpResponse {
+async fn register_user(server: &TestUserServer, email: &str, password: &str) -> SignUpResponse {
     let params = SignUpParams {
         email: email.to_string(),
         name: "annie".to_string(),

+ 2 - 7
backend/tests/api/workspace.rs

@@ -2,12 +2,7 @@ use crate::helper::*;
 use flowy_workspace::entities::{
     app::{DeleteAppParams, QueryAppParams, UpdateAppParams},
     view::{DeleteViewParams, QueryViewParams, UpdateViewParams},
-    workspace::{
-        CreateWorkspaceParams,
-        DeleteWorkspaceParams,
-        QueryWorkspaceParams,
-        UpdateWorkspaceParams,
-    },
+    workspace::{CreateWorkspaceParams, DeleteWorkspaceParams, QueryWorkspaceParams, UpdateWorkspaceParams},
 };
 
 #[actix_rt::test]
@@ -173,7 +168,7 @@ async fn view_delete() {
 
 #[actix_rt::test]
 async fn workspace_list_read() {
-    let mut server = spawn_server().await;
+    let mut server = spawn_user_server().await;
     let token = server.register_user().await.token;
     server.user_token = Some(token);
     for i in 0..3 {

+ 97 - 0
backend/tests/document/helper.rs

@@ -0,0 +1,97 @@
+use crate::helper::*;
+use flowy_document::{
+    entities::doc::{CreateDocParams, DocDelta, QueryDocParams},
+    module::FlowyDocument,
+    services::doc::edit_doc_context::EditDocContext,
+};
+use flowy_infra::uuid;
+use flowy_ot::core::Delta;
+use flowy_sdk::{FlowySDK, FlowySDKConfig};
+use flowy_test::{prelude::root_dir, FlowyTestSDK};
+use flowy_user::{entities::SignUpParams, services::user::UserSession};
+use flowy_workspace::prelude::DOC_DEFAULT_DATA;
+use std::{str::FromStr, sync::Arc};
+
+pub struct DocumentTest {
+    server: TestServer,
+    sdk: FlowyTestSDK,
+    flowy_document: Arc<FlowyDocument>,
+    user_session: Arc<UserSession>,
+    edit_context: Arc<EditDocContext>,
+}
+
+#[derive(Clone)]
+pub enum DocScript {
+    SendText(&'static str),
+    SendBinary(Vec<u8>),
+}
+
+async fn create_doc(user_session: Arc<UserSession>, flowy_document: Arc<FlowyDocument>) -> Arc<EditDocContext> {
+    let conn = user_session.db_pool().unwrap().get().unwrap();
+    let doc_id = uuid();
+    let params = CreateDocParams {
+        id: doc_id.clone(),
+        data: DOC_DEFAULT_DATA.to_string(),
+    };
+    let _ = flowy_document.create(params, &*conn).unwrap();
+
+    let edit_context = flowy_document
+        .open(QueryDocParams { doc_id }, user_session.db_pool().unwrap())
+        .await
+        .unwrap();
+
+    edit_context
+}
+
+async fn init_user(user_session: Arc<UserSession>) {
+    let params = SignUpParams {
+        email: format!("{}@gmail.com", uuid()),
+        name: "nathan".to_string(),
+        password: "HelloWorld!@12".to_string(),
+    };
+
+    user_session.sign_up(params).await.unwrap();
+    user_session.init_user().await.unwrap();
+}
+
+impl DocumentTest {
+    pub async fn new() -> Self {
+        let server = spawn_server().await;
+        let config = FlowySDKConfig::new(&root_dir(), &server.host, "http", "ws").log_filter("debug");
+        let sdk = FlowySDK::new(config);
+
+        let flowy_document = sdk.flowy_document.clone();
+        let user_session = sdk.user_session.clone();
+
+        init_user(user_session.clone()).await;
+
+        let edit_context = create_doc(user_session.clone(), flowy_document.clone()).await;
+
+        Self {
+            server,
+            sdk,
+            flowy_document,
+            user_session,
+            edit_context,
+        }
+    }
+
+    pub async fn run_scripts(self, scripts: Vec<DocScript>) {
+        for script in scripts {
+            match script {
+                DocScript::SendText(s) => {
+                    let delta = Delta::from_str(s).unwrap();
+                    let data = delta.to_json();
+                    let doc_delta = DocDelta {
+                        doc_id: self.edit_context.doc_id.clone(),
+                        data,
+                    };
+
+                    self.flowy_document.apply_doc_delta(doc_delta).await;
+                },
+                DocScript::SendBinary(_bytes) => {},
+            }
+        }
+        std::mem::forget(self);
+    }
+}

+ 0 - 0
backend/tests/ws/mod.rs → backend/tests/document/mod.rs


+ 12 - 0
backend/tests/document/ws.rs

@@ -0,0 +1,12 @@
+use crate::document::helper::{DocScript, DocumentTest};
+use tokio::time::{interval, Duration};
+
+#[actix_rt::test]
+async fn ws_connect() {
+    let test = DocumentTest::new().await;
+    test.run_scripts(vec![DocScript::SendText("abc")]).await;
+
+    let mut interval = interval(Duration::from_secs(10));
+    interval.tick().await;
+    interval.tick().await;
+}

+ 55 - 83
backend/tests/helper.rs

@@ -12,7 +12,7 @@ use flowy_workspace::prelude::{server::*, *};
 use sqlx::{Connection, Executor, PgConnection, PgPool};
 use uuid::Uuid;
 
-pub struct TestServer {
+pub struct TestUserServer {
     pub host: String,
     pub port: u16,
     pub pg_pool: PgPool,
@@ -20,9 +20,9 @@ pub struct TestServer {
     pub user_id: Option<String>,
 }
 
-impl TestServer {
+impl TestUserServer {
     pub async fn new() -> Self {
-        let mut server = spawn_server().await;
+        let mut server: TestUserServer = spawn_server().await.into();
         let response = server.register_user().await;
         server.user_token = Some(response.token);
         server.user_id = Some(response.user_id);
@@ -36,28 +36,16 @@ impl TestServer {
 
     pub async fn sign_out(&self) {
         let url = format!("{}/api/auth", self.http_addr());
-        let _ = user_sign_out_request(self.user_token(), &url)
-            .await
-            .unwrap();
+        let _ = user_sign_out_request(self.user_token(), &url).await.unwrap();
     }
 
-    pub fn user_token(&self) -> &str {
-        self.user_token
-            .as_ref()
-            .expect("must call register_user first ")
-    }
+    pub fn user_token(&self) -> &str { self.user_token.as_ref().expect("must call register_user first ") }
 
-    pub fn user_id(&self) -> &str {
-        self.user_id
-            .as_ref()
-            .expect("must call register_user first ")
-    }
+    pub fn user_id(&self) -> &str { self.user_id.as_ref().expect("must call register_user first ") }
 
     pub async fn get_user_profile(&self) -> UserProfile {
         let url = format!("{}/api/user", self.http_addr());
-        let user_profile = get_user_profile_request(self.user_token(), &url)
-            .await
-            .unwrap();
+        let user_profile = get_user_profile_request(self.user_token(), &url).await.unwrap();
         user_profile
     }
 
@@ -68,99 +56,73 @@ impl TestServer {
 
     pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> Workspace {
         let url = format!("{}/api/workspace", self.http_addr());
-        let workspace = create_workspace_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        let workspace = create_workspace_request(self.user_token(), params, &url).await.unwrap();
         workspace
     }
 
     pub async fn read_workspaces(&self, params: QueryWorkspaceParams) -> RepeatedWorkspace {
         let url = format!("{}/api/workspace", self.http_addr());
-        let workspaces = read_workspaces_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        let workspaces = read_workspaces_request(self.user_token(), params, &url).await.unwrap();
         workspaces
     }
 
     pub async fn update_workspace(&self, params: UpdateWorkspaceParams) {
         let url = format!("{}/api/workspace", self.http_addr());
-        update_workspace_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        update_workspace_request(self.user_token(), params, &url).await.unwrap();
     }
 
     pub async fn delete_workspace(&self, params: DeleteWorkspaceParams) {
         let url = format!("{}/api/workspace", self.http_addr());
-        delete_workspace_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        delete_workspace_request(self.user_token(), params, &url).await.unwrap();
     }
 
     pub async fn create_app(&self, params: CreateAppParams) -> App {
         let url = format!("{}/api/app", self.http_addr());
-        let app = create_app_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        let app = create_app_request(self.user_token(), params, &url).await.unwrap();
         app
     }
 
     pub async fn read_app(&self, params: QueryAppParams) -> Option<App> {
         let url = format!("{}/api/app", self.http_addr());
-        let app = read_app_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        let app = read_app_request(self.user_token(), params, &url).await.unwrap();
         app
     }
 
     pub async fn update_app(&self, params: UpdateAppParams) {
         let url = format!("{}/api/app", self.http_addr());
-        update_app_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        update_app_request(self.user_token(), params, &url).await.unwrap();
     }
 
     pub async fn delete_app(&self, params: DeleteAppParams) {
         let url = format!("{}/api/app", self.http_addr());
-        delete_app_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        delete_app_request(self.user_token(), params, &url).await.unwrap();
     }
 
     pub async fn create_view(&self, params: CreateViewParams) -> View {
         let url = format!("{}/api/view", self.http_addr());
-        let view = create_view_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        let view = create_view_request(self.user_token(), params, &url).await.unwrap();
         view
     }
 
     pub async fn read_view(&self, params: QueryViewParams) -> Option<View> {
         let url = format!("{}/api/view", self.http_addr());
-        let view = read_view_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        let view = read_view_request(self.user_token(), params, &url).await.unwrap();
         view
     }
 
     pub async fn update_view(&self, params: UpdateViewParams) {
         let url = format!("{}/api/view", self.http_addr());
-        update_view_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        update_view_request(self.user_token(), params, &url).await.unwrap();
     }
 
     pub async fn delete_view(&self, params: DeleteViewParams) {
         let url = format!("{}/api/view", self.http_addr());
-        delete_view_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        delete_view_request(self.user_token(), params, &url).await.unwrap();
     }
 
     pub async fn read_doc(&self, params: QueryDocParams) -> Option<Doc> {
         let url = format!("{}/api/doc", self.http_addr());
-        let doc = read_doc_request(self.user_token(), params, &url)
-            .await
-            .unwrap();
+        let doc = read_doc_request(self.user_token(), params, &url).await.unwrap();
         doc
     }
 
@@ -182,14 +144,32 @@ impl TestServer {
 
     pub fn http_addr(&self) -> String { format!("http://{}", self.host) }
 
-    pub fn ws_addr(&self) -> String {
-        format!(
-            "ws://{}/ws/{}",
-            self.host,
-            self.user_token.as_ref().unwrap()
-        )
+    pub fn ws_addr(&self) -> String { format!("ws://{}/ws/{}", self.host, self.user_token.as_ref().unwrap()) }
+}
+
+impl std::convert::From<TestServer> for TestUserServer {
+    fn from(server: TestServer) -> Self {
+        TestUserServer {
+            host: server.host,
+            port: server.port,
+            pg_pool: server.pg_pool,
+            user_token: None,
+            user_id: None,
+        }
     }
 }
+
+pub async fn spawn_user_server() -> TestUserServer {
+    let server: TestUserServer = spawn_server().await.into();
+    server
+}
+
+pub struct TestServer {
+    pub host: String,
+    pub port: u16,
+    pub pg_pool: PgPool,
+}
+
 pub async fn spawn_server() -> TestServer {
     let database_name = format!("{}", Uuid::new_v4().to_string());
     let configuration = {
@@ -217,8 +197,6 @@ pub async fn spawn_server() -> TestServer {
         pg_pool: get_connection_pool(&configuration.database)
             .await
             .expect("Failed to connect to the database"),
-        user_token: None,
-        user_id: None,
     }
 }
 
@@ -265,7 +243,7 @@ async fn drop_test_database(database_name: String) {
         .expect("Failed to drop database.");
 }
 
-pub async fn create_test_workspace(server: &TestServer) -> Workspace {
+pub async fn create_test_workspace(server: &TestUserServer) -> Workspace {
     let params = CreateWorkspaceParams {
         name: "My first workspace".to_string(),
         desc: "This is my first workspace".to_string(),
@@ -275,7 +253,7 @@ pub async fn create_test_workspace(server: &TestServer) -> Workspace {
     workspace
 }
 
-pub async fn create_test_app(server: &TestServer, workspace_id: &str) -> App {
+pub async fn create_test_app(server: &TestUserServer, workspace_id: &str) -> App {
     let params = CreateAppParams {
         workspace_id: workspace_id.to_owned(),
         name: "My first app".to_string(),
@@ -287,7 +265,7 @@ pub async fn create_test_app(server: &TestServer, workspace_id: &str) -> App {
     app
 }
 
-pub async fn create_test_view(application: &TestServer, app_id: &str) -> View {
+pub async fn create_test_view(application: &TestUserServer, app_id: &str) -> View {
     let name = "My first view".to_string();
     let desc = "This is my first view".to_string();
     let thumbnail = "http://1.png".to_string();
@@ -298,43 +276,37 @@ pub async fn create_test_view(application: &TestServer, app_id: &str) -> View {
 }
 
 pub struct WorkspaceTest {
-    pub server: TestServer,
+    pub server: TestUserServer,
     pub workspace: Workspace,
 }
 
 impl WorkspaceTest {
     pub async fn new() -> Self {
-        let server = TestServer::new().await;
+        let server = TestUserServer::new().await;
         let workspace = create_test_workspace(&server).await;
         Self { server, workspace }
     }
 
-    pub async fn create_app(&self) -> App {
-        create_test_app(&self.server, &self.workspace.id).await
-    }
+    pub async fn create_app(&self) -> App { create_test_app(&self.server, &self.workspace.id).await }
 }
 
 pub struct AppTest {
-    pub server: TestServer,
+    pub server: TestUserServer,
     pub workspace: Workspace,
     pub app: App,
 }
 
 impl AppTest {
     pub async fn new() -> Self {
-        let server = TestServer::new().await;
+        let server = TestUserServer::new().await;
         let workspace = create_test_workspace(&server).await;
         let app = create_test_app(&server, &workspace.id).await;
-        Self {
-            server,
-            workspace,
-            app,
-        }
+        Self { server, workspace, app }
     }
 }
 
 pub struct ViewTest {
-    pub server: TestServer,
+    pub server: TestUserServer,
     pub workspace: Workspace,
     pub app: App,
     pub view: View,
@@ -342,7 +314,7 @@ pub struct ViewTest {
 
 impl ViewTest {
     pub async fn new() -> Self {
-        let server = TestServer::new().await;
+        let server = TestUserServer::new().await;
         let workspace = create_test_workspace(&server).await;
         let app = create_test_app(&server, &workspace.id).await;
         let view = create_test_view(&server, &app.id).await;

+ 1 - 1
backend/tests/main.rs

@@ -1,3 +1,3 @@
 mod api;
+mod document;
 pub mod helper;
-mod ws;

+ 0 - 76
backend/tests/ws/helper.rs

@@ -1,76 +0,0 @@
-use crate::helper::TestServer;
-use flowy_ws::{WsController, WsModule, WsSender, WsState};
-use parking_lot::RwLock;
-use std::sync::Arc;
-
-pub struct WsTest {
-    server: TestServer,
-    ws_controller: Arc<RwLock<WsController>>,
-}
-
-#[derive(Clone)]
-pub enum WsScript {
-    SendText(&'static str),
-    SendBinary(Vec<u8>),
-    Disconnect(&'static str),
-}
-
-impl WsTest {
-    pub async fn new(scripts: Vec<WsScript>) -> Self {
-        let server = TestServer::new().await;
-        let ws_controller = Arc::new(RwLock::new(WsController::new()));
-        ws_controller
-            .write()
-            .state_callback(move |state| match state {
-                WsState::Connected(sender) => {
-                    WsScriptRunner {
-                        scripts: scripts.clone(),
-                        sender: sender.clone(),
-                        source: WsModule::Doc,
-                    }
-                    .run();
-                },
-                _ => {},
-            })
-            .await;
-
-        Self {
-            server,
-            ws_controller,
-        }
-    }
-
-    pub async fn run_scripts(&mut self) {
-        let addr = self.server.ws_addr();
-        self.ws_controller
-            .write()
-            .connect(addr)
-            .unwrap()
-            .await
-            .unwrap();
-    }
-}
-
-struct WsScriptRunner {
-    scripts: Vec<WsScript>,
-    sender: Arc<WsSender>,
-    source: WsModule,
-}
-
-impl WsScriptRunner {
-    fn run(self) {
-        for script in self.scripts {
-            match script {
-                WsScript::SendText(text) => {
-                    self.sender.send_text(&self.source, text).unwrap();
-                },
-                WsScript::SendBinary(bytes) => {
-                    self.sender.send_binary(&self.source, bytes).unwrap();
-                },
-                WsScript::Disconnect(reason) => {
-                    self.sender.send_disconnect(reason).unwrap();
-                },
-            }
-        }
-    }
-}

+ 0 - 13
backend/tests/ws/ws.rs

@@ -1,13 +0,0 @@
-use crate::ws::helper::{WsScript, WsTest};
-
-#[actix_rt::test]
-async fn ws_connect() {
-    let mut ws = WsTest::new(vec![
-        WsScript::SendText("abc"),
-        WsScript::SendText("abc"),
-        WsScript::SendText("abc"),
-        WsScript::Disconnect("close by user"),
-    ])
-    .await;
-    ws.run_scripts().await
-}

+ 0 - 0
flowy-test/temp/flowy/flowy.log.2021-09-27


+ 0 - 1
rust-lib/Cargo.toml

@@ -14,7 +14,6 @@ members = [
   "flowy-workspace",
   "flowy-observable",
   "flowy-document",
-  "flowy-editor",
   "flowy-ot",
   "flowy-net",
   "flowy-ws",

+ 10 - 2
rust-lib/dart-ffi/src/lib.rs

@@ -24,7 +24,10 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
     let c_str: &CStr = unsafe { CStr::from_ptr(path) };
     let path: &str = c_str.to_str().unwrap();
 
-    let config = FlowySDKConfig::new(path).log_filter("debug");
+    let host = "localhost";
+    let http_schema = "http";
+    let ws_schema = "ws";
+    let config = FlowySDKConfig::new(path, host, http_schema, ws_schema).log_filter("debug");
     *FLOWY_SDK.write() = Some(Arc::new(FlowySDK::new(config)));
 
     return 1;
@@ -33,7 +36,12 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
 #[no_mangle]
 pub extern "C" fn async_command(port: i64, input: *const u8, len: usize) {
     let request: ModuleRequest = FFIRequest::from_u8_pointer(input, len).into();
-    log::trace!("[FFI]: {} Async Event: {:?} with {} port", &request.id, &request.event, port);
+    log::trace!(
+        "[FFI]: {} Async Event: {:?} with {} port",
+        &request.id,
+        &request.event,
+        port
+    );
 
     let _ = EventDispatch::async_send_with_callback(dispatch(), request, move |resp: EventResponse| {
         log::trace!("[FFI]: Post data to dart through {} port", port);

+ 12 - 4
rust-lib/flowy-document/src/module.rs

@@ -8,7 +8,11 @@ use flowy_database::ConnectionPool;
 use crate::{
     entities::doc::{CreateDocParams, Doc, DocDelta, QueryDocParams},
     errors::DocError,
-    services::{doc::doc_controller::DocController, server::construct_doc_server, ws::WsDocumentManager},
+    services::{
+        doc::{doc_controller::DocController, edit_doc_context::EditDocContext},
+        server::construct_doc_server,
+        ws::WsDocumentManager,
+    },
 };
 
 pub trait DocumentUser: Send + Sync {
@@ -38,9 +42,13 @@ impl FlowyDocument {
         Ok(())
     }
 
-    pub async fn open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Doc, DocError> {
-        let open_doc = self.doc_ctrl.open(params, pool).await?;
-        Ok(open_doc.doc())
+    pub async fn open(
+        &self,
+        params: QueryDocParams,
+        pool: Arc<ConnectionPool>,
+    ) -> Result<Arc<EditDocContext>, DocError> {
+        let edit_context = self.doc_ctrl.open(params, pool).await?;
+        Ok(edit_context)
     }
 
     pub async fn apply_doc_delta(&self, params: DocDelta) -> Result<Doc, DocError> {

+ 3 - 8
rust-lib/flowy-document/src/services/doc/doc_controller.rs

@@ -2,18 +2,13 @@ use crate::{
     entities::doc::{CreateDocParams, Doc, DocDelta, QueryDocParams},
     errors::{internal_error, DocError},
     module::DocumentUser,
-    services::{
-        cache::DocCache,
-        doc::{edit_doc_context::EditDocContext, rev_manager::RevisionManager},
-        server::Server,
-        ws::WsDocumentManager,
-    },
-    sql_tables::doc::{DocTable, DocTableSql, OpTableSql},
+    services::{cache::DocCache, doc::edit_doc_context::EditDocContext, server::Server, ws::WsDocumentManager},
+    sql_tables::doc::{DocTable, DocTableSql},
 };
 use bytes::Bytes;
 use flowy_database::{ConnectionPool, SqliteConnection};
 use flowy_infra::future::{wrap_future, FnFuture};
-use flowy_ot::core::Delta;
+
 use parking_lot::RwLock;
 use std::sync::Arc;
 use tokio::time::{interval, Duration};

+ 3 - 3
rust-lib/flowy-document/src/services/doc/edit_doc_context.rs

@@ -24,8 +24,8 @@ use std::{convert::TryFrom, sync::Arc};
 
 pub type DocId = String;
 
-pub(crate) struct EditDocContext {
-    pub(crate) doc_id: DocId,
+pub struct EditDocContext {
+    pub doc_id: DocId,
     document: Arc<RwLock<Document>>,
     rev_manager: Arc<RevisionManager>,
     pool: Arc<ConnectionPool>,
@@ -49,7 +49,7 @@ impl EditDocContext {
         Ok(edit_context)
     }
 
-    pub(crate) fn doc(&self) -> Doc {
+    pub fn doc(&self) -> Doc {
         Doc {
             id: self.doc_id.clone(),
             data: self.document.read().to_json(),

+ 4 - 7
rust-lib/flowy-document/src/services/doc/rev_manager.rs

@@ -1,15 +1,12 @@
 use crate::{
     entities::doc::{RevType, Revision, RevisionRange},
     errors::{internal_error, DocError},
-    services::{
-        util::RevIdCounter,
-        ws::{WsDocumentHandler, WsDocumentSender},
-    },
-    sql_tables::{OpTableSql, RevChangeset, RevState, RevTable},
+    services::{util::RevIdCounter, ws::WsDocumentSender},
+    sql_tables::{OpTableSql, RevChangeset, RevState},
 };
-use dashmap::{DashMap, DashSet};
+use dashmap::DashSet;
 use flowy_database::ConnectionPool;
-use parking_lot::{lock_api::RwLockWriteGuard, RawRwLock, RwLock};
+use parking_lot::RwLock;
 use std::{
     collections::{HashMap, VecDeque},
     sync::Arc,

+ 3 - 6
rust-lib/flowy-document/src/sql_tables/doc/doc_op_sql.rs

@@ -1,15 +1,12 @@
 use crate::{
-    entities::doc::{RevType, Revision},
+    entities::doc::Revision,
     errors::DocError,
     sql_tables::{doc::RevTable, RevChangeset, RevState, RevTableType},
 };
-use diesel::{insert_into, select, update};
+use diesel::{insert_into, update};
 use flowy_database::{
     prelude::*,
-    schema::{
-        rev_table,
-        rev_table::{columns::*, dsl, dsl::doc_id},
-    },
+    schema::rev_table::{columns::*, dsl, dsl::doc_id},
     SqliteConnection,
 };
 

+ 0 - 8
rust-lib/flowy-editor/Cargo.toml

@@ -1,8 +0,0 @@
-[package]
-name = "flowy-editor"
-version = "0.1.0"
-edition = "2018"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]

+ 0 - 7
rust-lib/flowy-editor/src/lib.rs

@@ -1,7 +0,0 @@
-#[cfg(test)]
-mod tests {
-    #[test]
-    fn it_works() {
-        assert_eq!(2 + 2, 4);
-    }
-}

+ 38 - 0
rust-lib/flowy-net/src/config.rs

@@ -4,6 +4,43 @@ pub const HOST: &'static str = "localhost:8000";
 pub const SCHEMA: &'static str = "http://";
 pub const HEADER_TOKEN: &'static str = "token";
 
+#[derive(Debug, Clone)]
+pub struct ServerConfig {
+    http_schema: String,
+    host: String,
+    ws_schema: String,
+}
+
+impl ServerConfig {
+    pub fn new(host: &str, http_schema: &str, ws_schema: &str) -> Self {
+        Self {
+            http_schema: http_schema.to_owned(),
+            host: host.to_owned(),
+            ws_schema: ws_schema.to_owned(),
+        }
+    }
+
+    fn scheme(&self) -> String { format!("{}://", self.http_schema) }
+
+    pub fn sign_up_url(&self) -> String { format!("{}{}/api/register", self.scheme(), self.host) }
+
+    pub fn sign_in_url(&self) -> String { format!("{}{}/api/auth", self.scheme(), self.host) }
+
+    pub fn sign_out_url(&self) -> String { format!("{}{}/api/auth", self.scheme(), self.host) }
+
+    pub fn user_profile_url(&self) -> String { format!("{}{}/api/user", self.scheme(), self.host) }
+
+    pub fn workspace_url(&self) -> String { format!("{}{}/api/workspace", self.scheme(), self.host) }
+
+    pub fn app_url(&self) -> String { format!("{}{}/api/app", self.scheme(), self.host) }
+
+    pub fn view_url(&self) -> String { format!("{}{}/api/view", self.scheme(), self.host) }
+
+    pub fn doc_url(&self) -> String { format!("{}{}/api/doc", self.scheme(), self.host) }
+
+    pub fn ws_addr(&self) -> String { format!("{}://{}/ws", self.ws_schema, self.host) }
+}
+
 lazy_static! {
     pub static ref SIGN_UP_URL: String = format!("{}/{}/api/register", SCHEMA, HOST);
     pub static ref SIGN_IN_URL: String = format!("{}/{}/api/auth", SCHEMA, HOST);
@@ -16,5 +53,6 @@ lazy_static! {
     pub static ref VIEW_URL: String = format!("{}/{}/api/view", SCHEMA, HOST);
     pub static ref DOC_URL: String = format!("{}/{}/api/doc", SCHEMA, HOST);
 
+    //
     pub static ref WS_ADDR: String = format!("ws://{}/ws", HOST);
 }

+ 1 - 0
rust-lib/flowy-sdk/Cargo.toml

@@ -14,6 +14,7 @@ flowy-workspace = { path = "../flowy-workspace" }
 flowy-database = { path = "../flowy-database" }
 flowy-document = { path = "../flowy-document" }
 flowy-ws = { path = "../flowy-ws" }
+flowy-net = { path = "../flowy-net" }
 tracing = { version = "0.1" }
 log = "0.4.14"
 futures-core = { version = "0.3", default-features = false }

+ 24 - 10
rust-lib/flowy-sdk/src/lib.rs

@@ -3,6 +3,9 @@ mod deps_resolve;
 pub mod module;
 
 use flowy_dispatch::prelude::*;
+use flowy_document::prelude::FlowyDocument;
+use flowy_net::config::ServerConfig;
+use flowy_user::services::user::{UserSession, UserSessionBuilder};
 use module::build_modules;
 pub use module::*;
 use std::sync::{
@@ -16,13 +19,16 @@ static INIT_LOG: AtomicBool = AtomicBool::new(false);
 pub struct FlowySDKConfig {
     root: String,
     log_filter: String,
+    server_config: ServerConfig,
 }
 
 impl FlowySDKConfig {
-    pub fn new(root: &str) -> Self {
+    pub fn new(root: &str, host: &str, http_schema: &str, ws_schema: &str) -> Self {
+        let server_config = ServerConfig::new(host, http_schema, ws_schema);
         FlowySDKConfig {
             root: root.to_owned(),
             log_filter: crate_log_filter(None),
+            server_config,
         }
     }
 
@@ -49,7 +55,9 @@ fn crate_log_filter(level: Option<String>) -> String {
 #[derive(Clone)]
 pub struct FlowySDK {
     config: FlowySDKConfig,
-    dispatch: Arc<EventDispatch>,
+    pub user_session: Arc<UserSession>,
+    pub flowy_document: Arc<FlowyDocument>,
+    pub dispatch: Arc<EventDispatch>,
 }
 
 impl FlowySDK {
@@ -58,9 +66,21 @@ impl FlowySDK {
         init_kv(&config.root);
 
         tracing::debug!("🔥 {:?}", config);
-        let dispatch = Arc::new(init_dispatch(&config.root));
+        let user_session = Arc::new(
+            UserSessionBuilder::new()
+                .root_dir(&config.root, &config.server_config)
+                .build(),
+        );
+        let flowy_document = build_document_module(user_session.clone());
+        let modules = build_modules(&config.server_config, user_session.clone(), flowy_document.clone());
+        let dispatch = Arc::new(EventDispatch::construct(|| modules));
 
-        Self { config, dispatch }
+        Self {
+            config,
+            user_session,
+            flowy_document,
+            dispatch,
+        }
     }
 
     pub fn dispatch(&self) -> Arc<EventDispatch> { self.dispatch.clone() }
@@ -83,9 +103,3 @@ fn init_log(config: &FlowySDKConfig) {
             .build();
     }
 }
-
-fn init_dispatch(root: &str) -> EventDispatch {
-    let config = ModuleConfig { root: root.to_owned() };
-    let dispatch = EventDispatch::construct(|| build_modules(config));
-    dispatch
-}

+ 19 - 16
rust-lib/flowy-sdk/src/module.rs

@@ -1,31 +1,34 @@
-use flowy_dispatch::prelude::Module;
-
 use crate::deps_resolve::{DocumentDepsResolver, WorkspaceDepsResolver};
+use flowy_dispatch::prelude::Module;
 use flowy_document::module::FlowyDocument;
-use flowy_user::services::user::{UserSession, UserSessionBuilder};
-
+use flowy_net::config::ServerConfig;
+use flowy_user::services::user::UserSession;
 use std::sync::Arc;
 
-pub struct ModuleConfig {
-    pub root: String,
-}
-
-pub fn build_modules(config: ModuleConfig) -> Vec<Module> {
-    let user_session = Arc::new(UserSessionBuilder::new().root_dir(&config.root).build());
-    vec![build_user_module(user_session.clone()), build_workspace_module(user_session)]
+pub fn build_modules(
+    server_config: &ServerConfig,
+    user_session: Arc<UserSession>,
+    flowy_document: Arc<FlowyDocument>,
+) -> Vec<Module> {
+    vec![
+        build_user_module(user_session.clone()),
+        build_workspace_module(&server_config, user_session, flowy_document),
+    ]
 }
 
 fn build_user_module(user_session: Arc<UserSession>) -> Module { flowy_user::module::create(user_session.clone()) }
 
-fn build_workspace_module(user_session: Arc<UserSession>) -> Module {
+fn build_workspace_module(
+    server_config: &ServerConfig,
+    user_session: Arc<UserSession>,
+    flowy_document: Arc<FlowyDocument>,
+) -> Module {
     let workspace_deps = WorkspaceDepsResolver::new(user_session.clone());
     let (user, database) = workspace_deps.split_into();
-    let document = build_document_module(user_session.clone());
-
-    flowy_workspace::module::create(user, database, document)
+    flowy_workspace::module::create(user, database, flowy_document, server_config)
 }
 
-fn build_document_module(user_session: Arc<UserSession>) -> Arc<FlowyDocument> {
+pub fn build_document_module(user_session: Arc<UserSession>) -> Arc<FlowyDocument> {
     let document_deps = DocumentDepsResolver::new(user_session.clone());
     let (user, ws_manager) = document_deps.split_into();
     let document = Arc::new(FlowyDocument::new(user, ws_manager));

+ 11 - 2
rust-lib/flowy-test/src/lib.rs

@@ -21,7 +21,12 @@ pub struct FlowyEnv {
 
 impl FlowyEnv {
     pub fn setup() -> Self {
-        let sdk = init_test_sdk();
+        let host = "localhost";
+        let http_schema = "http";
+        let ws_schema = "ws";
+
+        let config = FlowySDKConfig::new(&root_dir(), host, http_schema, ws_schema).log_filter("debug");
+        let sdk = FlowySDK::new(config);
         let result = sign_up(sdk.dispatch());
         let env = Self {
             sdk,
@@ -35,6 +40,10 @@ impl FlowyEnv {
 }
 
 pub fn init_test_sdk() -> FlowyTestSDK {
-    let config = FlowySDKConfig::new(&root_dir()).log_filter("debug");
+    let host = "localhost";
+    let http_schema = "http";
+    let ws_schema = "ws";
+
+    let config = FlowySDKConfig::new(&root_dir(), host, http_schema, ws_schema).log_filter("debug");
     FlowySDK::new(config)
 }

+ 12 - 3
rust-lib/flowy-user/src/errors.rs

@@ -2,7 +2,6 @@ use bytes::Bytes;
 use derive_more::Display;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_dispatch::prelude::{EventResponse, ResponseBuilder};
-
 use std::{convert::TryInto, fmt::Debug};
 
 #[derive(Debug, Default, Clone, ProtoBuf)]
@@ -27,9 +26,19 @@ macro_rules! static_user_error {
 }
 
 impl UserError {
-    pub(crate) fn new(code: ErrorCode, msg: &str) -> Self { Self { code, msg: msg.to_owned() } }
+    pub(crate) fn new(code: ErrorCode, msg: &str) -> Self {
+        Self {
+            code,
+            msg: msg.to_owned(),
+        }
+    }
 
-    pub(crate) fn code(code: ErrorCode) -> Self { Self { code, msg: "".to_owned() } }
+    pub(crate) fn code(code: ErrorCode) -> Self {
+        Self {
+            code,
+            msg: "".to_owned(),
+        }
+    }
 
     pub fn context<T: Debug>(mut self, error: T) -> Self {
         self.msg = format!("{:?}", error);

+ 6 - 3
rust-lib/flowy-user/src/services/server/mod.rs

@@ -10,6 +10,7 @@ use crate::{
     errors::UserError,
 };
 use flowy_infra::future::ResultFuture;
+use flowy_net::config::ServerConfig;
 
 pub trait UserServerAPI {
     fn sign_up(&self, params: SignUpParams) -> ResultFuture<SignUpResponse, UserError>;
@@ -17,12 +18,14 @@ pub trait UserServerAPI {
     fn sign_out(&self, token: &str) -> ResultFuture<(), UserError>;
     fn update_user(&self, token: &str, params: UpdateUserParams) -> ResultFuture<(), UserError>;
     fn get_user(&self, token: &str) -> ResultFuture<UserProfile, UserError>;
+    fn ws_addr(&self) -> String;
 }
 
-pub(crate) fn construct_user_server() -> Arc<dyn UserServerAPI + Send + Sync> {
+pub(crate) fn construct_user_server(config: &ServerConfig) -> Arc<dyn UserServerAPI + Send + Sync> {
     if cfg!(feature = "http_server") {
-        Arc::new(UserServer {})
+        Arc::new(UserServer::new(config.clone()))
     } else {
-        Arc::new(UserServerMock {})
+        // Arc::new(UserServerMock {})
+        Arc::new(UserServer::new(config.clone()))
     }
 }

+ 31 - 10
rust-lib/flowy-user/src/services/server/server_api.rs

@@ -10,37 +10,46 @@ use flowy_net::{
     request::{HttpRequestBuilder, ResponseMiddleware},
 };
 
-pub struct UserServer {}
+pub struct UserServer {
+    config: ServerConfig,
+}
 impl UserServer {
-    pub fn new() -> Self { Self {} }
+    pub fn new(config: ServerConfig) -> Self { Self { config } }
 }
 
 impl UserServerAPI for UserServer {
     fn sign_up(&self, params: SignUpParams) -> ResultFuture<SignUpResponse, UserError> {
-        ResultFuture::new(async move { user_sign_up_request(params, SIGN_UP_URL.as_ref()).await })
+        let url = self.config.sign_up_url();
+        ResultFuture::new(async move { user_sign_up_request(params, &url).await })
     }
 
     fn sign_in(&self, params: SignInParams) -> ResultFuture<SignInResponse, UserError> {
-        ResultFuture::new(async move { user_sign_in_request(params, SIGN_IN_URL.as_ref()).await })
+        let url = self.config.sign_in_url();
+        ResultFuture::new(async move { user_sign_in_request(params, &url).await })
     }
 
     fn sign_out(&self, token: &str) -> ResultFuture<(), UserError> {
         let token = token.to_owned();
+        let url = self.config.sign_out_url();
         ResultFuture::new(async move {
-            let _ = user_sign_out_request(&token, SIGN_OUT_URL.as_ref()).await;
+            let _ = user_sign_out_request(&token, &url).await;
             Ok(())
         })
     }
 
     fn update_user(&self, token: &str, params: UpdateUserParams) -> ResultFuture<(), UserError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { update_user_profile_request(&token, params, USER_PROFILE_URL.as_ref()).await })
+        let url = self.config.user_profile_url();
+        ResultFuture::new(async move { update_user_profile_request(&token, params, &url).await })
     }
 
     fn get_user(&self, token: &str) -> ResultFuture<UserProfile, UserError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { get_user_profile_request(&token, USER_PROFILE_URL.as_ref()).await })
+        let url = self.config.user_profile_url();
+        ResultFuture::new(async move { get_user_profile_request(&token, &url).await })
     }
+
+    fn ws_addr(&self) -> String { self.config.ws_addr() }
 }
 
 use crate::{errors::ErrorCode, observable::*};
@@ -72,17 +81,29 @@ impl ResponseMiddleware for Middleware {
 pub(crate) fn request_builder() -> HttpRequestBuilder { HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) }
 
 pub async fn user_sign_up_request(params: SignUpParams, url: &str) -> Result<SignUpResponse, UserError> {
-    let response = request_builder().post(&url.to_owned()).protobuf(params)?.response().await?;
+    let response = request_builder()
+        .post(&url.to_owned())
+        .protobuf(params)?
+        .response()
+        .await?;
     Ok(response)
 }
 
 pub async fn user_sign_in_request(params: SignInParams, url: &str) -> Result<SignInResponse, UserError> {
-    let response = request_builder().post(&url.to_owned()).protobuf(params)?.response().await?;
+    let response = request_builder()
+        .post(&url.to_owned())
+        .protobuf(params)?
+        .response()
+        .await?;
     Ok(response)
 }
 
 pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), UserError> {
-    let _ = request_builder().delete(&url.to_owned()).header(HEADER_TOKEN, token).send().await?;
+    let _ = request_builder()
+        .delete(&url.to_owned())
+        .header(HEADER_TOKEN, token)
+        .send()
+        .await?;
     Ok(())
 }
 

+ 5 - 1
rust-lib/flowy-user/src/services/server/server_api_mock.rs

@@ -37,9 +37,13 @@ impl UserServerAPI for UserServerMock {
 
     fn sign_out(&self, _token: &str) -> ResultFuture<(), UserError> { ResultFuture::new(async { Ok(()) }) }
 
-    fn update_user(&self, _token: &str, _params: UpdateUserParams) -> ResultFuture<(), UserError> { ResultFuture::new(async { Ok(()) }) }
+    fn update_user(&self, _token: &str, _params: UpdateUserParams) -> ResultFuture<(), UserError> {
+        ResultFuture::new(async { Ok(()) })
+    }
 
     fn get_user(&self, _token: &str) -> ResultFuture<UserProfile, UserError> {
         ResultFuture::new(async { Err(UserError::internal().context("mock data, ignore this error")) })
     }
+
+    fn ws_addr(&self) -> String { "ws://localhost:8000/ws/".to_owned() }
 }

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

@@ -1,4 +1,5 @@
 use crate::services::user::{SessionStatusCallback, UserSession, UserSessionConfig};
+use flowy_net::config::ServerConfig;
 use std::sync::Arc;
 
 pub struct UserSessionBuilder {
@@ -14,8 +15,8 @@ impl UserSessionBuilder {
         }
     }
 
-    pub fn root_dir(mut self, dir: &str) -> Self {
-        self.config = Some(UserSessionConfig::new(dir));
+    pub fn root_dir(mut self, dir: &str, server_config: &ServerConfig) -> Self {
+        self.config = Some(UserSessionConfig::new(dir, server_config));
         self
     }
 

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

@@ -17,6 +17,7 @@ use flowy_database::{
     UserDatabaseConnection,
 };
 use flowy_infra::kv::KV;
+use flowy_net::config::ServerConfig;
 use flowy_sqlite::ConnectionPool;
 use flowy_ws::{connect::Retry, WsController, WsMessage, WsMessageHandler, WsSender};
 use parking_lot::RwLock;
@@ -25,12 +26,14 @@ use std::{sync::Arc, time::Duration};
 
 pub struct UserSessionConfig {
     root_dir: String,
+    server_config: ServerConfig,
 }
 
 impl UserSessionConfig {
-    pub fn new(root_dir: &str) -> Self {
+    pub fn new(root_dir: &str, server_config: &ServerConfig) -> Self {
         Self {
             root_dir: root_dir.to_owned(),
+            server_config: server_config.clone(),
         }
     }
 }
@@ -54,7 +57,7 @@ pub struct UserSession {
 impl UserSession {
     pub fn new(config: UserSessionConfig, status_callback: SessionStatusCallback) -> Self {
         let db = UserDB::new(&config.root_dir);
-        let server = construct_user_server();
+        let server = construct_user_server(&config.server_config);
         let ws_controller = Arc::new(RwLock::new(WsController::new()));
         let user_session = Self {
             database: db,
@@ -120,7 +123,8 @@ impl UserSession {
     #[tracing::instrument(level = "debug", skip(self))]
     pub async fn sign_out(&self) -> Result<(), UserError> {
         let session = self.get_session()?;
-        let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))).execute(&*(self.db_connection()?))?;
+        let _ =
+            diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))).execute(&*(self.db_connection()?))?;
         let _ = self.database.close_user_db(&session.user_id)?;
         let _ = self.set_session(None)?;
         (self.status_callback)(SessionStatus::Expired {
@@ -178,7 +182,9 @@ impl UserSession {
 
     pub fn token(&self) -> Result<String, UserError> { Ok(self.get_session()?.token) }
 
-    pub fn add_ws_handler(&self, handler: Arc<dyn WsMessageHandler>) { let _ = self.ws_controller.write().add_handler(handler); }
+    pub fn add_ws_handler(&self, handler: Arc<dyn WsMessageHandler>) {
+        let _ = self.ws_controller.write().add_handler(handler);
+    }
 
     pub fn get_ws_sender(&self) -> Result<Arc<WsSender>, UserError> {
         match self.ws_controller.try_read_for(Duration::from_millis(300)) {
@@ -204,7 +210,9 @@ impl UserSession {
         tokio::spawn(async move {
             match server.get_user(&token).await {
                 Ok(profile) => {
-                    notify(&token, UserObservable::UserProfileUpdated).payload(profile).send();
+                    notify(&token, UserObservable::UserProfileUpdated)
+                        .payload(profile)
+                        .send();
                 },
                 Err(e) => {
                     notify(&token, UserObservable::UserProfileUpdated).error(e).send();
@@ -245,7 +253,9 @@ impl UserSession {
 
     async fn save_user(&self, user: UserTable) -> Result<UserTable, UserError> {
         let conn = self.db_connection()?;
-        let _ = diesel::insert_into(user_table::table).values(user.clone()).execute(&*conn)?;
+        let _ = diesel::insert_into(user_table::table)
+            .values(user.clone())
+            .execute(&*conn)?;
         Ok(user)
     }
 
@@ -285,7 +295,7 @@ impl UserSession {
     }
 
     fn start_ws_connection(&self, token: &str) -> Result<(), UserError> {
-        let addr = format!("{}/{}", flowy_net::config::WS_ADDR.as_str(), token);
+        let addr = format!("{}/{}", self.server.ws_addr(), token);
         let ws_controller = self.ws_controller.clone();
         let retry = Retry::new(&addr, move |addr| {
             let _ = ws_controller.write().connect(addr.to_owned());
@@ -296,7 +306,11 @@ impl UserSession {
     }
 }
 
-pub async fn update_user(_server: Server, pool: Arc<ConnectionPool>, params: UpdateUserParams) -> Result<(), UserError> {
+pub async fn update_user(
+    _server: Server,
+    pool: Arc<ConnectionPool>,
+    params: UpdateUserParams,
+) -> Result<(), UserError> {
     let changeset = UserTableChangeset::new(params);
     let conn = pool.get()?;
     diesel_update_table!(user_table, changeset, &*conn);

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

@@ -68,9 +68,9 @@ pub struct CreateViewParams {
     pub data: String,
 }
 
-pub const VIEW_DEFAULT_DATA: &str = "[{\"insert\":\"\\n\"}]";
+pub const DOC_DEFAULT_DATA: &str = "[{\"insert\":\"\\n\"}]";
 #[allow(dead_code)]
-pub fn default_delta() -> Vec<u8> { VIEW_DEFAULT_DATA.as_bytes().to_vec() }
+pub fn default_delta() -> Vec<u8> { DOC_DEFAULT_DATA.as_bytes().to_vec() }
 
 impl CreateViewParams {
     pub fn new(belong_to_id: String, name: String, desc: String, view_type: ViewType, thumbnail: String) -> Self {
@@ -80,7 +80,7 @@ impl CreateViewParams {
             desc,
             thumbnail,
             view_type,
-            data: VIEW_DEFAULT_DATA.to_string(),
+            data: DOC_DEFAULT_DATA.to_string(),
         }
     }
 }

+ 14 - 3
rust-lib/flowy-workspace/src/module.rs

@@ -8,6 +8,7 @@ use crate::{
 use flowy_database::DBConnection;
 use flowy_dispatch::prelude::*;
 use flowy_document::module::FlowyDocument;
+use flowy_net::config::ServerConfig;
 use flowy_sqlite::ConnectionPool;
 use std::sync::Arc;
 
@@ -28,9 +29,19 @@ pub trait WorkspaceDatabase: Send + Sync {
     }
 }
 
-pub fn create(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>, document: Arc<FlowyDocument>) -> Module {
-    let server = construct_workspace_server();
-    let view_controller = Arc::new(ViewController::new(user.clone(), database.clone(), server.clone(), document));
+pub fn create(
+    user: Arc<dyn WorkspaceUser>,
+    database: Arc<dyn WorkspaceDatabase>,
+    flowy_document: Arc<FlowyDocument>,
+    server_config: &ServerConfig,
+) -> Module {
+    let server = construct_workspace_server(server_config);
+    let view_controller = Arc::new(ViewController::new(
+        user.clone(),
+        database.clone(),
+        server.clone(),
+        flowy_document,
+    ));
     let app_controller = Arc::new(AppController::new(user.clone(), database.clone(), server.clone()));
 
     let workspace_controller = Arc::new(WorkspaceController::new(

+ 8 - 3
rust-lib/flowy-workspace/src/services/server/mod.rs

@@ -22,6 +22,7 @@ use crate::{
     errors::WorkspaceError,
 };
 use flowy_infra::future::ResultFuture;
+use flowy_net::config::ServerConfig;
 use std::sync::Arc;
 
 pub(crate) type Server = Arc<dyn WorkspaceServerAPI + Send + Sync>;
@@ -30,7 +31,11 @@ pub trait WorkspaceServerAPI {
     // Workspace
     fn create_workspace(&self, token: &str, params: CreateWorkspaceParams) -> ResultFuture<Workspace, WorkspaceError>;
 
-    fn read_workspace(&self, token: &str, params: QueryWorkspaceParams) -> ResultFuture<RepeatedWorkspace, WorkspaceError>;
+    fn read_workspace(
+        &self,
+        token: &str,
+        params: QueryWorkspaceParams,
+    ) -> ResultFuture<RepeatedWorkspace, WorkspaceError>;
 
     fn update_workspace(&self, token: &str, params: UpdateWorkspaceParams) -> ResultFuture<(), WorkspaceError>;
 
@@ -55,9 +60,9 @@ pub trait WorkspaceServerAPI {
     fn delete_app(&self, token: &str, params: DeleteAppParams) -> ResultFuture<(), WorkspaceError>;
 }
 
-pub(crate) fn construct_workspace_server() -> Arc<dyn WorkspaceServerAPI + Send + Sync> {
+pub(crate) fn construct_workspace_server(config: &ServerConfig) -> Arc<dyn WorkspaceServerAPI + Send + Sync> {
     if cfg!(feature = "http_server") {
-        Arc::new(WorkspaceServer {})
+        Arc::new(WorkspaceServer::new(config.clone()))
     } else {
         Arc::new(WorkspaceServerMock {})
     }

+ 64 - 20
rust-lib/flowy-workspace/src/services/server/server_api.rs

@@ -17,72 +17,100 @@ use crate::{
 use flowy_infra::future::ResultFuture;
 use flowy_net::{config::*, request::HttpRequestBuilder};
 
-pub struct WorkspaceServer {}
+pub struct WorkspaceServer {
+    config: ServerConfig,
+}
+
+impl WorkspaceServer {
+    pub fn new(config: ServerConfig) -> WorkspaceServer { Self { config } }
+}
 
 impl WorkspaceServerAPI for WorkspaceServer {
     fn create_workspace(&self, token: &str, params: CreateWorkspaceParams) -> ResultFuture<Workspace, WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { create_workspace_request(&token, params, WORKSPACE_URL.as_ref()).await })
+        let url = self.config.workspace_url();
+        ResultFuture::new(async move { create_workspace_request(&token, params, &url).await })
     }
 
-    fn read_workspace(&self, token: &str, params: QueryWorkspaceParams) -> ResultFuture<RepeatedWorkspace, WorkspaceError> {
+    fn read_workspace(
+        &self,
+        token: &str,
+        params: QueryWorkspaceParams,
+    ) -> ResultFuture<RepeatedWorkspace, WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { read_workspaces_request(&token, params, WORKSPACE_URL.as_ref()).await })
+        let url = self.config.workspace_url();
+        ResultFuture::new(async move { read_workspaces_request(&token, params, &url).await })
     }
 
     fn update_workspace(&self, token: &str, params: UpdateWorkspaceParams) -> ResultFuture<(), WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { update_workspace_request(&token, params, WORKSPACE_URL.as_ref()).await })
+        let url = self.config.workspace_url();
+        ResultFuture::new(async move { update_workspace_request(&token, params, &url).await })
     }
 
     fn delete_workspace(&self, token: &str, params: DeleteWorkspaceParams) -> ResultFuture<(), WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { delete_workspace_request(&token, params, WORKSPACE_URL.as_ref()).await })
+        let url = self.config.workspace_url();
+        ResultFuture::new(async move { delete_workspace_request(&token, params, &url).await })
     }
 
     fn create_view(&self, token: &str, params: CreateViewParams) -> ResultFuture<View, WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { create_view_request(&token, params, VIEW_URL.as_ref()).await })
+        let url = self.config.view_url();
+        ResultFuture::new(async move { create_view_request(&token, params, &url).await })
     }
 
     fn read_view(&self, token: &str, params: QueryViewParams) -> ResultFuture<Option<View>, WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { read_view_request(&token, params, VIEW_URL.as_ref()).await })
+        let url = self.config.view_url();
+        ResultFuture::new(async move { read_view_request(&token, params, &url).await })
     }
 
     fn delete_view(&self, token: &str, params: DeleteViewParams) -> ResultFuture<(), WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { delete_view_request(&token, params, VIEW_URL.as_ref()).await })
+        let url = self.config.view_url();
+        ResultFuture::new(async move { delete_view_request(&token, params, &url).await })
     }
 
     fn update_view(&self, token: &str, params: UpdateViewParams) -> ResultFuture<(), WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { update_view_request(&token, params, VIEW_URL.as_ref()).await })
+        let url = self.config.view_url();
+        ResultFuture::new(async move { update_view_request(&token, params, &url).await })
     }
 
     fn create_app(&self, token: &str, params: CreateAppParams) -> ResultFuture<App, WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { create_app_request(&token, params, APP_URL.as_ref()).await })
+        let url = self.config.app_url();
+        ResultFuture::new(async move { create_app_request(&token, params, &url).await })
     }
 
     fn read_app(&self, token: &str, params: QueryAppParams) -> ResultFuture<Option<App>, WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { read_app_request(&token, params, APP_URL.as_ref()).await })
+        let url = self.config.app_url();
+        ResultFuture::new(async move { read_app_request(&token, params, &url).await })
     }
 
     fn update_app(&self, token: &str, params: UpdateAppParams) -> ResultFuture<(), WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { update_app_request(&token, params, APP_URL.as_ref()).await })
+        let url = self.config.app_url();
+        ResultFuture::new(async move { update_app_request(&token, params, &url).await })
     }
 
     fn delete_app(&self, token: &str, params: DeleteAppParams) -> ResultFuture<(), WorkspaceError> {
         let token = token.to_owned();
-        ResultFuture::new(async move { delete_app_request(&token, params, APP_URL.as_ref()).await })
+        let url = self.config.app_url();
+        ResultFuture::new(async move { delete_app_request(&token, params, &url).await })
     }
 }
 
-pub(crate) fn request_builder() -> HttpRequestBuilder { HttpRequestBuilder::new().middleware(super::middleware::MIDDLEWARE.clone()) }
-pub async fn create_workspace_request(token: &str, params: CreateWorkspaceParams, url: &str) -> Result<Workspace, WorkspaceError> {
+pub(crate) fn request_builder() -> HttpRequestBuilder {
+    HttpRequestBuilder::new().middleware(super::middleware::MIDDLEWARE.clone())
+}
+pub async fn create_workspace_request(
+    token: &str,
+    params: CreateWorkspaceParams,
+    url: &str,
+) -> Result<Workspace, WorkspaceError> {
     let workspace = request_builder()
         .post(&url.to_owned())
         .header(HEADER_TOKEN, token)
@@ -92,7 +120,11 @@ pub async fn create_workspace_request(token: &str, params: CreateWorkspaceParams
     Ok(workspace)
 }
 
-pub async fn read_workspaces_request(token: &str, params: QueryWorkspaceParams, url: &str) -> Result<RepeatedWorkspace, WorkspaceError> {
+pub async fn read_workspaces_request(
+    token: &str,
+    params: QueryWorkspaceParams,
+    url: &str,
+) -> Result<RepeatedWorkspace, WorkspaceError> {
     let repeated_workspace = request_builder()
         .get(&url.to_owned())
         .header(HEADER_TOKEN, token)
@@ -103,7 +135,11 @@ pub async fn read_workspaces_request(token: &str, params: QueryWorkspaceParams,
     Ok(repeated_workspace)
 }
 
-pub async fn update_workspace_request(token: &str, params: UpdateWorkspaceParams, url: &str) -> Result<(), WorkspaceError> {
+pub async fn update_workspace_request(
+    token: &str,
+    params: UpdateWorkspaceParams,
+    url: &str,
+) -> Result<(), WorkspaceError> {
     let _ = request_builder()
         .patch(&url.to_owned())
         .header(HEADER_TOKEN, token)
@@ -113,7 +149,11 @@ pub async fn update_workspace_request(token: &str, params: UpdateWorkspaceParams
     Ok(())
 }
 
-pub async fn delete_workspace_request(token: &str, params: DeleteWorkspaceParams, url: &str) -> Result<(), WorkspaceError> {
+pub async fn delete_workspace_request(
+    token: &str,
+    params: DeleteWorkspaceParams,
+    url: &str,
+) -> Result<(), WorkspaceError> {
     let _ = request_builder()
         .delete(url)
         .header(HEADER_TOKEN, token)
@@ -176,7 +216,11 @@ pub async fn create_view_request(token: &str, params: CreateViewParams, url: &st
     Ok(view)
 }
 
-pub async fn read_view_request(token: &str, params: QueryViewParams, url: &str) -> Result<Option<View>, WorkspaceError> {
+pub async fn read_view_request(
+    token: &str,
+    params: QueryViewParams,
+    url: &str,
+) -> Result<Option<View>, WorkspaceError> {
     let view = request_builder()
         .get(&url.to_owned())
         .header(HEADER_TOKEN, token)

+ 9 - 4
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -50,7 +50,8 @@ impl ViewController {
         // TODO: rollback anything created before if failed?
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let _ = self.save_view(view.clone(), conn)?;
-            self.document.create(CreateDocParams::new(&view.id, params.data), conn)?;
+            self.document
+                .create(CreateDocParams::new(&view.id, params.data), conn)?;
 
             let repeated_view = self.read_local_views_belong_to(&view.belong_to_id, conn)?;
             notify(&view.belong_to_id, WorkspaceObservable::AppCreateView)
@@ -78,8 +79,8 @@ impl ViewController {
 
     #[tracing::instrument(level = "debug", skip(self), err)]
     pub(crate) async fn open_view(&self, params: QueryDocParams) -> Result<Doc, WorkspaceError> {
-        let doc = self.document.open(params, self.database.db_pool()?).await?;
-        Ok(doc)
+        let edit_context = self.document.open(params, self.database.db_pool()?).await?;
+        Ok(edit_context.doc())
     }
 
     pub(crate) async fn delete_view(&self, params: DeleteViewParams) -> Result<(), WorkspaceError> {
@@ -191,7 +192,11 @@ impl ViewController {
     }
 
     // belong_to_id will be the app_id or view_id.
-    fn read_local_views_belong_to(&self, belong_to_id: &str, conn: &SqliteConnection) -> Result<RepeatedView, WorkspaceError> {
+    fn read_local_views_belong_to(
+        &self,
+        belong_to_id: &str,
+        conn: &SqliteConnection,
+    ) -> Result<RepeatedView, WorkspaceError> {
         let views = self
             .sql
             .read_views_belong_to(belong_to_id, conn)?

+ 20 - 6
rust-lib/flowy-ws/src/ws.rs

@@ -103,7 +103,7 @@ impl WsController {
 
     pub fn get_sender(&self) -> Result<Arc<WsSender>, WsError> {
         match &self.sender {
-            None => Err(WsError::internal().context("WsSender is not initialized")),
+            None => Err(WsError::internal().context("WsSender is not initialized, should call connect first")),
             Some(sender) => Ok(sender.clone()),
         }
     }
@@ -112,7 +112,10 @@ impl WsController {
         log::debug!("🐴 ws connect: {}", &addr);
         let (connection, handlers) = self.make_connect(addr.clone());
         let state_notify = self.state_notify.clone();
-        let sender = self.sender.clone().expect("Sender should be not empty after calling make_connect");
+        let sender = self
+            .sender
+            .clone()
+            .expect("Sender should be not empty after calling make_connect");
         Ok(tokio::spawn(async move {
             match connection.await {
                 Ok(stream) => {
@@ -158,7 +161,10 @@ impl WsController {
         let handlers = self.handlers.clone();
         self.sender = Some(Arc::new(WsSender { ws_tx }));
         self.addr = Some(addr.clone());
-        (WsConnectionFuture::new(msg_tx, ws_rx, addr), WsHandlerFuture::new(handlers, msg_rx))
+        (
+            WsConnectionFuture::new(msg_tx, ws_rx, addr),
+            WsHandlerFuture::new(handlers, msg_rx),
+        )
     }
 }
 
@@ -170,7 +176,9 @@ pub struct WsHandlerFuture {
 }
 
 impl WsHandlerFuture {
-    fn new(handlers: HashMap<WsModule, Arc<dyn WsMessageHandler>>, msg_rx: MsgReceiver) -> Self { Self { msg_rx, handlers } }
+    fn new(handlers: HashMap<WsModule, Arc<dyn WsMessageHandler>>, msg_rx: MsgReceiver) -> Self {
+        Self { msg_rx, handlers }
+    }
 
     fn handler_ws_message(&self, message: Message) {
         match message {
@@ -215,7 +223,10 @@ pub struct WsSender {
 impl WsSender {
     pub fn send_msg<T: Into<WsMessage>>(&self, msg: T) -> Result<(), WsError> {
         let msg = msg.into();
-        let _ = self.ws_tx.unbounded_send(msg.into()).map_err(|e| WsError::internal().context(e))?;
+        let _ = self
+            .ws_tx
+            .unbounded_send(msg.into())
+            .map_err(|e| WsError::internal().context(e))?;
         Ok(())
     }
 
@@ -241,7 +252,10 @@ impl WsSender {
             reason: reason.to_owned().into(),
         };
         let msg = Message::Close(Some(frame));
-        let _ = self.ws_tx.unbounded_send(msg).map_err(|e| WsError::internal().context(e))?;
+        let _ = self
+            .ws_tx
+            .unbounded_send(msg)
+            .map_err(|e| WsError::internal().context(e))?;
         Ok(())
     }
 }