From a61a5c58cca3f2c58470067bc6b33033f848f9a8 Mon Sep 17 00:00:00 2001 From: Khor Shu Heng <32997938+khorshuheng@users.noreply.github.com> Date: Fri, 3 Jan 2025 21:28:36 +0800 Subject: [PATCH] fix: auto patch workspace folder without space (#1107) --- Cargo.lock | 14 ++--- Cargo.toml | 14 ++--- src/api/workspace.rs | 7 ++- src/biz/collab/folder_view.rs | 4 +- src/biz/collab/ops.rs | 105 ++++++++++++++++++++++++++++++++- src/biz/collab/utils.rs | 3 + src/biz/workspace/page_view.rs | 16 ++--- 7 files changed, 135 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52cfc5ff..702e6427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2145,7 +2145,7 @@ dependencies = [ [[package]] name = "collab" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c45a2120361f94bbedb787cdd2192a38c94c7f5f#c45a2120361f94bbedb787cdd2192a38c94c7f5f" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d51fecb64762855f9e54648c78ab1ee0d5404f97#d51fecb64762855f9e54648c78ab1ee0d5404f97" dependencies = [ "anyhow", "arc-swap", @@ -2170,7 +2170,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c45a2120361f94bbedb787cdd2192a38c94c7f5f#c45a2120361f94bbedb787cdd2192a38c94c7f5f" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d51fecb64762855f9e54648c78ab1ee0d5404f97#d51fecb64762855f9e54648c78ab1ee0d5404f97" dependencies = [ "anyhow", "async-trait", @@ -2210,7 +2210,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c45a2120361f94bbedb787cdd2192a38c94c7f5f#c45a2120361f94bbedb787cdd2192a38c94c7f5f" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d51fecb64762855f9e54648c78ab1ee0d5404f97#d51fecb64762855f9e54648c78ab1ee0d5404f97" dependencies = [ "anyhow", "arc-swap", @@ -2231,7 +2231,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c45a2120361f94bbedb787cdd2192a38c94c7f5f#c45a2120361f94bbedb787cdd2192a38c94c7f5f" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d51fecb64762855f9e54648c78ab1ee0d5404f97#d51fecb64762855f9e54648c78ab1ee0d5404f97" dependencies = [ "anyhow", "bytes", @@ -2251,7 +2251,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c45a2120361f94bbedb787cdd2192a38c94c7f5f#c45a2120361f94bbedb787cdd2192a38c94c7f5f" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d51fecb64762855f9e54648c78ab1ee0d5404f97#d51fecb64762855f9e54648c78ab1ee0d5404f97" dependencies = [ "anyhow", "arc-swap", @@ -2273,7 +2273,7 @@ dependencies = [ [[package]] name = "collab-importer" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c45a2120361f94bbedb787cdd2192a38c94c7f5f#c45a2120361f94bbedb787cdd2192a38c94c7f5f" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d51fecb64762855f9e54648c78ab1ee0d5404f97#d51fecb64762855f9e54648c78ab1ee0d5404f97" dependencies = [ "anyhow", "async-recursion", @@ -2382,7 +2382,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c45a2120361f94bbedb787cdd2192a38c94c7f5f#c45a2120361f94bbedb787cdd2192a38c94c7f5f" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=d51fecb64762855f9e54648c78ab1ee0d5404f97#d51fecb64762855f9e54648c78ab1ee0d5404f97" dependencies = [ "anyhow", "collab", diff --git a/Cargo.toml b/Cargo.toml index 2e762414..5adac680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -320,13 +320,13 @@ lto = false [patch.crates-io] # It's diffcult to resovle different version with the same crate used in AppFlowy Frontend and the Client-API crate. # So using patch to workaround this issue. -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c45a2120361f94bbedb787cdd2192a38c94c7f5f" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c45a2120361f94bbedb787cdd2192a38c94c7f5f" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c45a2120361f94bbedb787cdd2192a38c94c7f5f" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c45a2120361f94bbedb787cdd2192a38c94c7f5f" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c45a2120361f94bbedb787cdd2192a38c94c7f5f" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c45a2120361f94bbedb787cdd2192a38c94c7f5f" } -collab-importer = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c45a2120361f94bbedb787cdd2192a38c94c7f5f" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d51fecb64762855f9e54648c78ab1ee0d5404f97" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d51fecb64762855f9e54648c78ab1ee0d5404f97" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d51fecb64762855f9e54648c78ab1ee0d5404f97" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d51fecb64762855f9e54648c78ab1ee0d5404f97" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d51fecb64762855f9e54648c78ab1ee0d5404f97" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d51fecb64762855f9e54648c78ab1ee0d5404f97" } +collab-importer = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "d51fecb64762855f9e54648c78ab1ee0d5404f97" } [features] history = [] diff --git a/src/api/workspace.rs b/src/api/workspace.rs index bfb908ed..fc85e299 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -2024,10 +2024,13 @@ async fn get_workspace_folder_handler( user_uuid: UserUuid, workspace_id: web::Path, state: Data, + server: Data, query: web::Query, + req: HttpRequest, ) -> Result>> { let depth = query.depth.unwrap_or(1); let uid = state.user_cache.get_user_uid(&user_uuid).await?; + let user = realtime_user_for_web_request(req.headers(), uid)?; let workspace_id = workspace_id.into_inner(); state .workspace_access_control @@ -2039,9 +2042,11 @@ async fn get_workspace_folder_handler( workspace_id.to_string() }; let folder_view = biz::collab::ops::get_user_workspace_structure( + &state.metrics.appflowy_web_metrics, + server, &state.collab_access_control_storage, &state.pg_pool, - uid, + user, workspace_id, depth, &root_view_id, diff --git a/src/biz/collab/folder_view.rs b/src/biz/collab/folder_view.rs index 86c425b0..48be52bd 100644 --- a/src/biz/collab/folder_view.rs +++ b/src/biz/collab/folder_view.rs @@ -2,9 +2,7 @@ use std::collections::HashSet; use app_error::AppError; use chrono::DateTime; -use collab_folder::{ - hierarchy_builder::SpacePermission, Folder, SectionItem, ViewLayout as CollabFolderViewLayout, -}; +use collab_folder::{Folder, SectionItem, SpacePermission, ViewLayout as CollabFolderViewLayout}; use shared_entity::dto::workspace_dto::{ self, FavoriteFolderView, FolderView, FolderViewMinimal, RecentFolderView, TrashFolderView, ViewLayout, diff --git a/src/biz/collab/ops.rs b/src/biz/collab/ops.rs index 851988be..b75b71cf 100644 --- a/src/biz/collab/ops.rs +++ b/src/biz/collab/ops.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; +use actix_web::web::Data; use app_error::AppError; use appflowy_collaborate::collab::storage::CollabAccessControlStorage; use chrono::DateTime; @@ -24,8 +25,12 @@ use collab_database::workspace_database::WorkspaceDatabaseBody; use collab_document::document::Document; use collab_entity::CollabType; use collab_entity::EncodedCollab; +use collab_folder::hierarchy_builder::NestedChildViewBuilder; use collab_folder::CollabOrigin; +use collab_folder::Folder; use collab_folder::SectionItem; +use collab_folder::SpaceInfo; +use collab_rt_entity::user::RealtimeUser; use database::collab::select_last_updated_database_row_ids; use database::collab::select_workspace_database_oid; use database::collab::{CollabStorage, GetCollabOrigin}; @@ -48,8 +53,12 @@ use sqlx::PgPool; use std::ops::DerefMut; use yrs::Map; +use crate::api::metrics::AppFlowyWebMetrics; +use crate::api::ws::RealtimeServerAddr; +use crate::biz::collab::folder_view::check_if_view_is_space; use crate::biz::collab::utils::get_database_row_doc_changes; use crate::biz::workspace::ops::broadcast_update_with_timeout; +use crate::biz::workspace::page_view::update_workspace_folder_data; use access_control::collab::CollabAccessControl; use anyhow::Context; use database_entity::dto::{ @@ -82,6 +91,8 @@ use super::utils::type_options_serde; use super::utils::write_to_database_row; use super::utils::CreatedRowDocument; use super::utils::DocChanges; +use super::utils::DEFAULT_SPACE_ICON; +use super::utils::DEFAULT_SPACE_ICON_COLOR; /// Create a new collab member /// If the collab member already exists, return [AppError::RecordAlreadyExists] @@ -293,10 +304,95 @@ pub async fn get_user_trash_folder_views( Ok(section_items_to_trash_folder_view(§ion_items, &folder)) } +#[allow(clippy::too_many_arguments)] +fn patch_old_workspace_folder( + user: RealtimeUser, + workspace_id: &str, + folder: &mut Folder, + child_view_id_without_space: &[String], +) -> Result, AppError> { + let encoded_update = { + let space_id = Uuid::new_v4().to_string(); + + let space_view = NestedChildViewBuilder::new(user.uid, workspace_id.to_string()) + .with_view_id(space_id.clone()) + .with_name("General") + .with_extra(|extra| { + extra + .with_space_info(SpaceInfo { + space_icon: Some(DEFAULT_SPACE_ICON.to_string()), + space_icon_color: Some(DEFAULT_SPACE_ICON_COLOR.to_string()), + ..Default::default() + }) + .build() + }) + .build() + .view; + let mut txn = folder.collab.transact_mut(); + folder.body.views.insert(&mut txn, space_view, None); + for (i, current_view_id) in child_view_id_without_space.iter().enumerate() { + let previous_view_id = if i == 0 { + None + } else { + Some(child_view_id_without_space[i - 1].clone()) + }; + folder + .body + .move_nested_view(&mut txn, current_view_id, &space_id, previous_view_id); + } + txn.encode_update_v1() + }; + Ok(encoded_update) +} + +async fn fix_old_workspace_folder( + appflowy_web_metrics: &AppFlowyWebMetrics, + server: Data, + user: RealtimeUser, + mut folder: Folder, + workspace_id: Uuid, +) -> Result { + let root_view = folder.get_view(&workspace_id.to_string()).ok_or_else(|| { + AppError::InvalidRequest(format!( + "Failed to get view for workspace_id: {}", + workspace_id + )) + })?; + let direct_workspace_children: Vec = root_view + .children + .iter() + .map(|view_id| view_id.to_string()) + .collect(); + let has_at_least_one_space = direct_workspace_children + .iter() + .filter_map(|view_id| folder.get_view(view_id)) + .any(|view| check_if_view_is_space(&view)); + if !has_at_least_one_space { + let folder_update = patch_old_workspace_folder( + user.clone(), + &workspace_id.to_string(), + &mut folder, + &direct_workspace_children, + )?; + update_workspace_folder_data( + appflowy_web_metrics, + server, + user, + workspace_id, + folder_update, + ) + .await?; + } + Ok(folder) +} + +#[allow(clippy::too_many_arguments)] pub async fn get_user_workspace_structure( + appflowy_web_metrics: &AppFlowyWebMetrics, + server: Data, collab_storage: &CollabAccessControlStorage, pg_pool: &PgPool, - uid: i64, + user: RealtimeUser, workspace_id: Uuid, depth: u32, root_view_id: &str, @@ -310,10 +406,13 @@ pub async fn get_user_workspace_structure( } let folder = get_latest_collab_folder( collab_storage, - GetCollabOrigin::User { uid }, + GetCollabOrigin::User { uid: user.uid }, &workspace_id.to_string(), ) .await?; + let patched_folder = + fix_old_workspace_folder(appflowy_web_metrics, server, user, folder, workspace_id).await?; + let publish_view_ids = select_published_view_ids_for_workspace(pg_pool, workspace_id).await?; let publish_view_ids: HashSet = publish_view_ids .into_iter() @@ -322,7 +421,7 @@ pub async fn get_user_workspace_structure( collab_folder_to_folder_view( workspace_id, root_view_id, - &folder, + &patched_folder, depth, &publish_view_ids, ) diff --git a/src/biz/collab/utils.rs b/src/biz/collab/utils.rs index 510432e2..666ee319 100644 --- a/src/biz/collab/utils.rs +++ b/src/biz/collab/utils.rs @@ -34,6 +34,9 @@ use std::collections::HashSet; use std::sync::Arc; use yrs::Map; +pub const DEFAULT_SPACE_ICON: &str = "interface_essential/home-3"; +pub const DEFAULT_SPACE_ICON_COLOR: &str = "0xFFA34AFD"; + pub fn get_row_details_serde( row_detail: RowDetail, field_by_id_name_uniq: &HashMap, diff --git a/src/biz/workspace/page_view.rs b/src/biz/workspace/page_view.rs index b88c2f50..124cde07 100644 --- a/src/biz/workspace/page_view.rs +++ b/src/biz/workspace/page_view.rs @@ -36,7 +36,7 @@ use collab_document::document::Document; use collab_document::document_data::default_document_data; use collab_entity::{CollabType, EncodedCollab}; use collab_folder::hierarchy_builder::NestedChildViewBuilder; -use collab_folder::{timestamp, CollabOrigin, Folder}; +use collab_folder::{timestamp, CollabOrigin, Folder, SpaceInfo}; use collab_rt_entity::user::RealtimeUser; use database::collab::{select_workspace_database_oid, CollabStorage, GetCollabOrigin}; use database::publish::select_published_view_ids_for_workspace; @@ -419,12 +419,14 @@ async fn add_new_space_to_folder( .with_view_id(view_id) .with_name(name) .with_extra(|builder| { - let mut extra = builder - .is_space(true, to_space_permission(space_permission)) - .build(); - extra["space_icon_color"] = json!(space_icon_color); - extra["space_icon"] = json!(space_icon); - extra + builder + .with_space_info(SpaceInfo { + space_icon: Some(space_icon.to_string()), + space_icon_color: Some(space_icon_color.to_string()), + space_permission: to_space_permission(space_permission), + ..Default::default() + }) + .build() }) .build() .view;