From d342039cfdb8c31a3228a0f2b5a763512203500a Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Fri, 16 Aug 2024 16:05:15 +0800 Subject: [PATCH 1/2] chore: create user awareness when creating a new user (#728) * chore: create user awareness when creating a new user * chore: fix client api compile --- Cargo.lock | 20 ++++ Cargo.toml | 153 ++++++++++++------------ libs/client-api-entity/Cargo.toml | 1 + libs/client-api-entity/src/id.rs | 8 ++ libs/client-api-entity/src/lib.rs | 2 + libs/client-api-test/Cargo.toml | 1 + libs/client-api-test/src/test_client.rs | 40 ++++++- src/biz/user/user_init.rs | 65 +++++++++- src/biz/user/user_verify.rs | 1 + src/biz/workspace/ops.rs | 1 + tests/user/mod.rs | 1 + tests/user/user_awareness_test.rs | 8 ++ 12 files changed, 220 insertions(+), 81 deletions(-) create mode 100644 libs/client-api-entity/src/id.rs create mode 100644 tests/user/user_awareness_test.rs diff --git a/Cargo.lock b/Cargo.lock index 1d3a01ba..6f99c3e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,6 +579,7 @@ dependencies = [ "collab-rt-entity", "collab-rt-protocol", "collab-stream", + "collab-user", "dashmap", "database", "database-entity", @@ -1954,6 +1955,7 @@ dependencies = [ "gotrue-entity", "infra", "shared-entity", + "uuid", ] [[package]] @@ -1969,6 +1971,7 @@ dependencies = [ "collab-document", "collab-entity", "collab-folder", + "collab-user", "database-entity", "dotenvy", "futures", @@ -2181,6 +2184,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "collab-user" +version = "0.2.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a6fe08ccf3e7ad782083848d6771a870da3e5af9#a6fe08ccf3e7ad782083848d6771a870da3e5af9" +dependencies = [ + "anyhow", + "collab", + "collab-entity", + "getrandom 0.2.15", + "parking_lot 0.12.3", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "color_quant" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0b138caa..4cc68284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,9 @@ edition = "2021" actix.workspace = true actix-web.workspace = true actix-http = { workspace = true, default-features = false, features = [ - "openssl", - "compress-brotli", - "compress-gzip", + "openssl", + "compress-brotli", + "compress-gzip", ] } actix-rt = "2.9.0" actix-web-actors = { version = "4.3" } @@ -27,20 +27,20 @@ serde_repr.workspace = true serde.workspace = true tokio = { workspace = true, features = [ - "macros", - "rt-multi-thread", - "sync", - "fs", - "time", - "full", + "macros", + "rt-multi-thread", + "sync", + "fs", + "time", + "full", ] } tokio-stream.workspace = true tokio-util = { version = "0.7.10", features = ["io"] } futures-util = { workspace = true, features = ["std", "io"] } once_cell = "1.19.0" chrono = { version = "0.4.37", features = [ - "serde", - "clock", + "serde", + "clock", ], default-features = false } derive_more = { version = "0.99" } secrecy.workspace = true @@ -48,10 +48,10 @@ rand = { version = "0.8", features = ["std_rng"] } anyhow = "1.0.79" thiserror = "1.0.56" reqwest = { workspace = true, features = [ - "json", - "rustls-tls", - "cookies", - "stream", + "json", + "rustls-tls", + "cookies", + "stream", ] } unicode-segmentation = "1.10" lazy_static.workspace = true @@ -61,31 +61,31 @@ bytes = "1.5.0" rcgen = { version = "0.10.0", features = ["pem", "x509-parser"] } mime = "0.3.17" aws-sdk-s3 = { version = "1.36.0", features = [ - "behavior-version-latest", - "rt-tokio", + "behavior-version-latest", + "rt-tokio", ] } aws-config = { version = "1.5.1", features = ["behavior-version-latest"] } redis = { workspace = true, features = [ - "json", - "tokio-comp", - "connection-manager", + "json", + "tokio-comp", + "connection-manager", ] } tracing = { version = "0.1.40", features = ["log"] } tracing-subscriber = { version = "0.3.18", features = [ - "registry", - "env-filter", - "ansi", - "json", - "tracing-log", + "registry", + "env-filter", + "ansi", + "json", + "tracing-log", ] } tracing-bunyan-formatter = "0.3.9" sqlx = { workspace = true, default-features = false, features = [ - "runtime-tokio-rustls", - "macros", - "postgres", - "uuid", - "chrono", - "migrate", + "runtime-tokio-rustls", + "macros", + "postgres", + "uuid", + "chrono", + "migrate", ] } async-trait.workspace = true prometheus-client.workspace = true @@ -114,6 +114,7 @@ collab = { workspace = true } collab-document = { workspace = true } collab-entity = { workspace = true } collab-folder = { workspace = true } +collab-user = { workspace = true } collab-rt-protocol.workspace = true #Local crate @@ -127,10 +128,10 @@ authentication.workspace = true access-control.workspace = true workspace-access.workspace = true app-error = { workspace = true, features = [ - "sqlx_error", - "actix_web_error", - "tokio_error", - "appflowy_ai_error", + "sqlx_error", + "actix_web_error", + "tokio_error", + "appflowy_ai_error", ] } shared-entity = { path = "libs/shared-entity", features = ["cloud"] } workspace-template = { workspace = true } @@ -152,11 +153,11 @@ assert-json-diff = "2.0.2" scraper = "0.17.1" client-api-test = { path = "libs/client-api-test", features = ["collab-sync"] } client-api = { path = "libs/client-api", features = [ - "collab-sync", - "test_util", - "sync_verbose_log", - "test_fast_sync", - "enable_brotli", + "collab-sync", + "test_util", + "sync_verbose_log", + "test_fast_sync", + "enable_brotli", ] } opener = "0.6.1" image = "0.23.14" @@ -175,37 +176,37 @@ path = "src/lib.rs" [workspace] members = [ - # libs - "libs/snowflake", - "libs/collab-rt-entity", - "libs/database", - "libs/database-entity", - "libs/client-api", - "libs/infra", - "libs/shared-entity", - "libs/gotrue", - "libs/gotrue-entity", - "admin_frontend", - "libs/app-error", - "libs/workspace-access", - "libs/workspace-template", - "libs/encrypt", - "libs/authentication", - "libs/access-control", - "libs/collab-rt-protocol", - "libs/collab-stream", - "libs/client-websocket", - "libs/client-api-test", - "libs/wasm-test", - "libs/client-api-wasm", - "libs/appflowy-ai-client", - "libs/client-api-entity", - # services - "services/appflowy-history", - "services/appflowy-collaborate", - # xtask - "xtask", - "libs/tonic-proto", + # libs + "libs/snowflake", + "libs/collab-rt-entity", + "libs/database", + "libs/database-entity", + "libs/client-api", + "libs/infra", + "libs/shared-entity", + "libs/gotrue", + "libs/gotrue-entity", + "admin_frontend", + "libs/app-error", + "libs/workspace-access", + "libs/workspace-template", + "libs/encrypt", + "libs/authentication", + "libs/access-control", + "libs/collab-rt-protocol", + "libs/collab-stream", + "libs/client-websocket", + "libs/client-api-test", + "libs/wasm-test", + "libs/client-api-wasm", + "libs/appflowy-ai-client", + "libs/client-api-entity", + # services + "services/appflowy-history", + "services/appflowy-collaborate", + # xtask + "xtask", + "libs/tonic-proto", ] [workspace.dependencies] @@ -230,13 +231,13 @@ serde_repr = "0.1.18" serde = { version = "1.0.195", features = ["derive"] } bytes = "1.5.0" workspace-template = { path = "libs/workspace-template" } -uuid = { version = "1.6.1", features = ["v4"] } +uuid = { version = "1.6.1", features = ["v4", "v5"] } anyhow = "1.0.79" actix = "0.13.3" actix-web = { version = "4.5.1", default-features = false, features = [ - "openssl", - "compress-brotli", - "compress-gzip", + "openssl", + "compress-brotli", + "compress-gzip", ] } actix-http = { version = "3.6.0", default-features = false } tokio = { version = "1.36.0", features = ["sync"] } @@ -267,6 +268,7 @@ collab = { version = "0.2.0" } collab-entity = { version = "0.2.0" } collab-folder = { version = "0.2.0" } collab-document = { version = "0.2.0" } +collab-user = { version = "0.2.0" } [profile.release] lto = true @@ -282,6 +284,7 @@ debug = true # So using patch to workaround this issue. collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6fe08ccf3e7ad782083848d6771a870da3e5af9" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6fe08ccf3e7ad782083848d6771a870da3e5af9" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6fe08ccf3e7ad782083848d6771a870da3e5af9" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6fe08ccf3e7ad782083848d6771a870da3e5af9" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a6fe08ccf3e7ad782083848d6771a870da3e5af9" } diff --git a/libs/client-api-entity/Cargo.toml b/libs/client-api-entity/Cargo.toml index c4c50173..bf748283 100644 --- a/libs/client-api-entity/Cargo.toml +++ b/libs/client-api-entity/Cargo.toml @@ -11,6 +11,7 @@ gotrue-entity = { workspace = true } shared-entity = { workspace = true } collab-rt-entity = { workspace = true } database-entity.workspace = true +uuid.workspace = true infra = { workspace = true, optional = true } diff --git a/libs/client-api-entity/src/id.rs b/libs/client-api-entity/src/id.rs new file mode 100644 index 00000000..1f50373a --- /dev/null +++ b/libs/client-api-entity/src/id.rs @@ -0,0 +1,8 @@ +use uuid::Uuid; + +pub fn user_awareness_object_id(user_uuid: &Uuid, workspace_id: &str) -> Uuid { + Uuid::new_v5( + user_uuid, + format!("user_awareness:{}", workspace_id).as_bytes(), + ) +} diff --git a/libs/client-api-entity/src/lib.rs b/libs/client-api-entity/src/lib.rs index 4d2fd53c..c7cb3ac3 100644 --- a/libs/client-api-entity/src/lib.rs +++ b/libs/client-api-entity/src/lib.rs @@ -1,3 +1,5 @@ +pub mod id; + pub use collab_entity::*; pub use collab_rt_entity::user::*; pub use database_entity::dto::*; diff --git a/libs/client-api-test/Cargo.toml b/libs/client-api-test/Cargo.toml index 7f95b817..00f70e2d 100644 --- a/libs/client-api-test/Cargo.toml +++ b/libs/client-api-test/Cargo.toml @@ -15,6 +15,7 @@ tracing.workspace = true collab-folder.workspace = true collab = { workspace = true } collab-document.workspace = true +collab-user.workspace = true client-api = { path = "../client-api", features = ["test_util"] } once_cell = "1.19.0" tempfile = "3.9.0" diff --git a/libs/client-api-test/src/test_client.rs b/libs/client-api-test/src/test_client.rs index c7d73261..42e91a87 100644 --- a/libs/client-api-test/src/test_client.rs +++ b/libs/client-api-test/src/test_client.rs @@ -14,12 +14,15 @@ use collab::core::origin::{CollabClient, CollabOrigin}; use collab::entity::EncodedCollab; use collab::preclude::Collab; +use crate::user::{generate_unique_registered_user, User}; +use client_api::entity::id::user_awareness_object_id; use collab_entity::CollabType; use collab_folder::Folder; +use collab_user::core::UserAwareness; use database_entity::dto::{ - AFAccessLevel, AFRole, AFSnapshotMeta, AFSnapshotMetas, AFUserWorkspaceInfo, AFWorkspace, - AFWorkspaceInvitationStatus, AFWorkspaceMember, BatchQueryCollabResult, CollabParams, - CreateCollabParams, InsertCollabMemberParams, QueryCollab, QueryCollabParams, + AFAccessLevel, AFRole, AFSnapshotMeta, AFSnapshotMetas, AFUserProfile, AFUserWorkspaceInfo, + AFWorkspace, AFWorkspaceInvitationStatus, AFWorkspaceMember, BatchQueryCollabResult, + CollabParams, CreateCollabParams, InsertCollabMemberParams, QueryCollab, QueryCollabParams, QuerySnapshotParams, SnapshotData, UpdateCollabMemberParams, }; use mime::Mime; @@ -40,8 +43,6 @@ use tokio_stream::StreamExt; use tracing::trace; use uuid::Uuid; -use crate::user::{generate_unique_registered_user, User}; - pub struct TestClient { pub user: User, pub ws_client: WSClient, @@ -187,6 +188,31 @@ impl TestClient { .unwrap() } + pub async fn get_user_awareness(&self) -> UserAwareness { + let workspace_id = self.workspace_id().await; + let profile = self.get_user_profile().await; + let awareness_object_id = user_awareness_object_id(&profile.uuid, &workspace_id).to_string(); + let data = self + .api_client + .get_collab(QueryCollabParams::new( + &awareness_object_id, + CollabType::UserAwareness, + &workspace_id, + )) + .await + .unwrap(); + let collab = Collab::new_with_source( + CollabOrigin::Empty, + &awareness_object_id, + DataSource::DocStateV1(data.encode_collab.doc_state.to_vec()), + vec![], + false, + ) + .unwrap(); + + UserAwareness::open(Arc::new(MutexCollab::new(collab)), None) + } + pub async fn try_update_workspace_member( &self, workspace_id: &str, @@ -406,6 +432,10 @@ impl TestClient { self.api_client.get_profile().await.unwrap().uid } + pub async fn get_user_profile(&self) -> AFUserProfile { + self.api_client.get_profile().await.unwrap() + } + pub async fn get_snapshot( &self, workspace_id: &str, diff --git a/src/biz/user/user_init.rs b/src/biz/user/user_init.rs index dd4b6da0..7240c0c1 100644 --- a/src/biz/user/user_init.rs +++ b/src/biz/user/user_init.rs @@ -1,16 +1,19 @@ use app_error::AppError; use appflowy_collaborate::collab::storage::CollabAccessControlStorage; +use collab::core::collab::MutexCollab; use collab::core::origin::CollabOrigin; use collab::preclude::{Any, Collab}; use collab_entity::define::WORKSPACE_DATABASES; use collab_entity::CollabType; +use collab_user::core::UserAwareness; use database::collab::CollabStorage; use database::pg_row::AFWorkspaceRow; use database_entity::dto::CollabParams; use sqlx::Transaction; use std::sync::Arc; -use tracing::{debug, instrument}; +use tracing::{debug, error, instrument, trace}; +use uuid::Uuid; use workspace_template::{WorkspaceTemplate, WorkspaceTemplateBuilder}; /// This function generates templates for a workspace and stores them in the database. @@ -18,6 +21,7 @@ use workspace_template::{WorkspaceTemplate, WorkspaceTemplateBuilder}; #[instrument(level = "debug", skip_all, err)] pub async fn initialize_workspace_for_user( uid: i64, + user_uuid: &Uuid, row: &AFWorkspaceRow, txn: &mut Transaction<'_, sqlx::Postgres>, templates: Vec, @@ -44,6 +48,16 @@ where txn, ) .await?; + + match create_user_awareness(&uid, user_uuid, &workspace_id, collab_storage, txn).await { + Ok(object_id) => trace!("User awareness created successfully: {}", object_id), + Err(err) => { + error!( + "Failed to create user awareness for workspace: {}, {}", + workspace_id, err + ); + }, + } } else { return Err(AppError::Internal(anyhow::anyhow!( "Workspace database object id is missing" @@ -75,6 +89,48 @@ where Ok(()) } +async fn create_user_awareness( + uid: &i64, + user_uuid: &Uuid, + workspace_id: &str, + storage: &Arc, + txn: &mut Transaction<'_, sqlx::Postgres>, +) -> Result { + let object_id = user_awareness_object_id(user_uuid, workspace_id).to_string(); + let collab_type = CollabType::UserAwareness; + let collab = Arc::new(MutexCollab::new(Collab::new_with_origin( + CollabOrigin::Empty, + object_id.clone(), + vec![], + false, + ))); + + // TODO(nathan): Maybe using hardcode encoded collab + let _ = UserAwareness::create(collab.clone(), None); + let encode_collab = collab + .lock() + .encode_collab_v1(|collab| collab_type.validate_require_data(collab)) + .map_err(AppError::Internal)?; + let encoded_collab_v1 = encode_collab + .encode_to_bytes() + .map_err(|err| AppError::Internal(anyhow::Error::from(err)))?; + + storage + .insert_new_collab_with_transaction( + workspace_id, + uid, + CollabParams { + object_id: object_id.to_string(), + encoded_collab_v1, + collab_type, + embeddings: None, + }, + txn, + ) + .await?; + Ok(object_id) +} + async fn create_workspace_database_collab( workspace_id: &str, uid: &i64, @@ -113,3 +169,10 @@ async fn create_workspace_database_collab( Ok(()) } + +pub fn user_awareness_object_id(user_uuid: &Uuid, workspace_id: &str) -> Uuid { + Uuid::new_v5( + user_uuid, + format!("user_awareness:{}", workspace_id).as_bytes(), + ) +} diff --git a/src/biz/user/user_verify.rs b/src/biz/user/user_verify.rs index 6107a12e..e1f79ec9 100644 --- a/src/biz/user/user_verify.rs +++ b/src/biz/user/user_verify.rs @@ -46,6 +46,7 @@ pub async fn verify_token(access_token: &str, state: &AppState) -> Result Date: Fri, 16 Aug 2024 20:48:43 +0800 Subject: [PATCH 2/2] chore: only log on debug --- services/appflowy-collaborate/src/indexer/document_indexer.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/appflowy-collaborate/src/indexer/document_indexer.rs b/services/appflowy-collaborate/src/indexer/document_indexer.rs index c1a8a699..fa1ed7b3 100644 --- a/services/appflowy-collaborate/src/indexer/document_indexer.rs +++ b/services/appflowy-collaborate/src/indexer/document_indexer.rs @@ -59,7 +59,9 @@ impl Indexer for DocumentIndexer { let (object_id, mut params) = match Self::get_document_contents(Arc::new(collab)) { Ok(result) => result, Err(err) => { - tracing::warn!("failed to get document data: {}", err); + if cfg!(debug_assertions) { + tracing::warn!("failed to get document:{} data: {}", err); + } return Ok(None); }, };