diff --git a/libs/client-api/src/http_view.rs b/libs/client-api/src/http_view.rs index 463651bd6..fcfd98a39 100644 --- a/libs/client-api/src/http_view.rs +++ b/libs/client-api/src/http_view.rs @@ -1,5 +1,6 @@ use client_api_entity::workspace_dto::{CreatePageParams, Page, PageCollab}; use reqwest::Method; +use serde_json::json; use shared_entity::response::{AppResponse, AppResponseError}; use uuid::Uuid; @@ -21,6 +22,24 @@ impl Client { AppResponse::::from_response(resp).await?.into_data() } + pub async fn move_workspace_page_view_to_trash( + &self, + workspace_id: Uuid, + view_id: String, + ) -> Result<(), AppResponseError> { + let url = format!( + "{}/api/workspace/{}/page-view/{}/move-to-trash", + self.base_url, workspace_id, view_id + ); + let resp = self + .http_client_with_auth(Method::POST, &url) + .await? + .json(&json!({})) + .send() + .await?; + AppResponse::<()>::from_response(resp).await?.into_error() + } + pub async fn get_workspace_page_view( &self, workspace_id: Uuid, diff --git a/src/api/workspace.rs b/src/api/workspace.rs index 65e7b2649..908664314 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -49,7 +49,7 @@ use crate::biz::workspace::ops::{ get_reactions_on_published_view, remove_comment_on_published_view, remove_reaction_on_comment, }; use crate::biz::workspace::page_view::{ - create_page, get_page_view_collab, update_page_collab_data, + create_page, get_page_view_collab, move_page_to_trash, update_page_collab_data, }; use crate::biz::workspace::publish::get_workspace_default_publish_view_info_meta; use crate::domain::compression::{ @@ -133,6 +133,10 @@ pub fn workspace_scope() -> Scope { web::resource("/{workspace_id}/page-view/{view_id}") .route(web::get().to(get_page_view_handler)), ) + .service( + web::resource("/{workspace_id}/page-view/{view_id}/move-to-trash") + .route(web::post().to(move_page_to_trash_handler)), + ) .service( web::resource("/{workspace_id}/batch/collab") .route(web::post().to(batch_create_collab_handler)), @@ -895,6 +899,24 @@ async fn post_page_view_handler( Ok(Json(AppResponse::Ok().with_data(page))) } +async fn move_page_to_trash_handler( + user_uuid: UserUuid, + path: web::Path<(Uuid, String)>, + state: Data, +) -> Result>> { + let uid = state.user_cache.get_user_uid(&user_uuid).await?; + let (workspace_uuid, view_id) = path.into_inner(); + move_page_to_trash( + &state.pg_pool, + &state.collab_access_control_storage, + uid, + workspace_uuid, + &view_id, + ) + .await?; + Ok(Json(AppResponse::Ok())) +} + async fn get_page_view_handler( user_uuid: UserUuid, path: web::Path<(Uuid, String)>, diff --git a/src/biz/workspace/page_view.rs b/src/biz/workspace/page_view.rs index 0dfd0fa61..5781003a1 100644 --- a/src/biz/workspace/page_view.rs +++ b/src/biz/workspace/page_view.rs @@ -14,6 +14,7 @@ use database::collab::{select_workspace_database_oid, CollabStorage, GetCollabOr use database::publish::select_published_view_ids_for_workspace; use database::user::select_web_user_from_uid; use database_entity::dto::{CollabParams, QueryCollab, QueryCollabParams, QueryCollabResult}; +use itertools::Itertools; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use shared_entity::dto::workspace_dto::{FolderView, Page, PageCollab, PageCollabData, ViewLayout}; use sqlx::{PgPool, Transaction}; @@ -93,6 +94,30 @@ async fn add_new_view_to_folder( }) } +async fn move_view_to_trash(view_id: &str, folder: &mut Folder) -> Result { + let mut current_view_and_descendants = folder + .get_views_belong_to(view_id) + .iter() + .map(|v| v.id.clone()) + .collect_vec(); + current_view_and_descendants.push(view_id.to_string()); + + let encoded_update = { + let mut txn = folder.collab.transact_mut(); + current_view_and_descendants.iter().for_each(|view_id| { + folder.body.views.update_view(&mut txn, view_id, |update| { + update.set_favorite(false).set_trash(true).done() + }); + }); + txn.encode_update_v1() + }; + + Ok(FolderUpdate { + updated_encoded_collab: folder_to_encoded_collab(folder)?, + encoded_updates: encoded_update, + }) +} + fn folder_to_encoded_collab(folder: &Folder) -> Result, AppError> { let collab_type = CollabType::Folder; let encoded_folder_collab = folder @@ -174,6 +199,34 @@ async fn create_document_page( Ok(Page { view_id }) } +pub async fn move_page_to_trash( + pg_pool: &PgPool, + collab_storage: &CollabAccessControlStorage, + uid: i64, + workspace_id: Uuid, + view_id: &str, +) -> Result<(), AppError> { + let collab_origin = GetCollabOrigin::User { uid }; + let mut folder = + get_latest_collab_folder(collab_storage, collab_origin, &workspace_id.to_string()).await?; + let trash_info = folder.get_my_trash_info(); + if trash_info.into_iter().any(|info| info.id == view_id) { + return Ok(()); + } + let folder_update = move_view_to_trash(view_id, &mut folder).await?; + let mut transaction = pg_pool.begin().await?; + insert_and_broadcast_workspace_folder_update( + uid, + workspace_id, + folder_update, + collab_storage, + &mut transaction, + ) + .await?; + transaction.commit().await?; + Ok(()) +} + pub async fn get_page_view_collab( pg_pool: &PgPool, collab_access_control_storage: &CollabAccessControlStorage, diff --git a/tests/workspace/page_view.rs b/tests/workspace/page_view.rs index 98a3a5ff4..9e3ac6413 100644 --- a/tests/workspace/page_view.rs +++ b/tests/workspace/page_view.rs @@ -1,8 +1,12 @@ use std::time::Duration; use client_api::entity::{QueryCollab, QueryCollabParams}; -use client_api_test::generate_unique_registered_user_client; +use client_api_test::{ + generate_unique_registered_user, generate_unique_registered_user_client, TestClient, +}; +use collab::{core::origin::CollabClient, preclude::Collab}; use collab_entity::CollabType; +use collab_folder::{CollabOrigin, Folder}; use shared_entity::dto::workspace_dto::{CreatePageParams, ViewLayout}; use tokio::time::sleep; use uuid::Uuid; @@ -96,3 +100,71 @@ async fn create_new_document_page() { .await .unwrap(); } + +#[tokio::test] +async fn move_page_to_trash() { + let registered_user = generate_unique_registered_user().await; + let mut app_client = TestClient::user_with_new_device(registered_user.clone()).await; + let web_client = TestClient::user_with_new_device(registered_user.clone()).await; + let workspace_id = app_client.workspace_id().await; + app_client.open_workspace_collab(&workspace_id).await; + app_client + .wait_object_sync_complete(&workspace_id) + .await + .unwrap(); + let folder_view = web_client + .api_client + .get_workspace_folder(&workspace_id.to_string(), Some(2), None) + .await + .unwrap(); + let general_space = &folder_view + .children + .into_iter() + .find(|v| v.name == "General") + .unwrap(); + let view_id_to_be_deleted = general_space.children[0].view_id.clone(); + web_client + .api_client + .move_workspace_page_view_to_trash( + Uuid::parse_str(&workspace_id).unwrap(), + view_id_to_be_deleted.clone(), + ) + .await + .unwrap(); + + // Wait for websocket to receive update + sleep(Duration::from_secs(1)).await; + let lock = app_client + .collabs + .get(&workspace_id) + .unwrap() + .collab + .read() + .await; + let collab: &Collab = (*lock).borrow(); + let collab_type = CollabType::Folder; + let encoded_collab = collab + .encode_collab_v1(|collab| collab_type.validate_require_data(collab)) + .unwrap(); + let uid = app_client.uid().await; + let folder = Folder::from_collab_doc_state( + uid, + CollabOrigin::Client(CollabClient::new(uid, app_client.device_id.clone())), + encoded_collab.into(), + &workspace_id, + vec![], + ) + .unwrap(); + assert!(folder + .get_my_trash_sections() + .iter() + .any(|v| v.id == view_id_to_be_deleted.clone())); + web_client + .api_client + .get_workspace_trash(&workspace_id) + .await + .unwrap() + .views + .iter() + .any(|v| v.view.view_id == view_id_to_be_deleted.clone()); +}