diff --git a/libs/client-api/src/http_view.rs b/libs/client-api/src/http_view.rs index dc179b4a..14101253 100644 --- a/libs/client-api/src/http_view.rs +++ b/libs/client-api/src/http_view.rs @@ -58,6 +58,23 @@ impl Client { AppResponse::<()>::from_response(resp).await?.into_error() } + pub async fn restore_all_workspace_page_views_from_trash( + &self, + workspace_id: Uuid, + ) -> Result<(), AppResponseError> { + let url = format!( + "{}/api/workspace/{}/restore-all-pages-from-trash", + self.base_url, workspace_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 update_workspace_page_view( &self, workspace_id: Uuid, diff --git a/src/api/workspace.rs b/src/api/workspace.rs index ad9b31dd..7aa29c82 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -49,8 +49,8 @@ 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, move_page_to_trash, restore_page_from_trash, update_page, - update_page_collab_data, + create_page, get_page_view_collab, move_page_to_trash, restore_all_pages_from_trash, + restore_page_from_trash, update_page, update_page_collab_data, }; use crate::biz::workspace::publish::get_workspace_default_publish_view_info_meta; use crate::domain::compression::{ @@ -143,6 +143,10 @@ pub fn workspace_scope() -> Scope { web::resource("/{workspace_id}/page-view/{view_id}/restore-from-trash") .route(web::post().to(restore_page_from_trash_handler)), ) + .service( + web::resource("/{workspace_id}/restore-all-pages-from-trash") + .route(web::post().to(restore_all_pages_from_trash_handler)), + ) .service( web::resource("/{workspace_id}/batch/collab") .route(web::post().to(batch_create_collab_handler)), @@ -941,6 +945,23 @@ async fn restore_page_from_trash_handler( Ok(Json(AppResponse::Ok())) } +async fn restore_all_pages_from_trash_handler( + user_uuid: UserUuid, + path: web::Path, + state: Data, +) -> Result>> { + let uid = state.user_cache.get_user_uid(&user_uuid).await?; + let workspace_uuid = path.into_inner(); + restore_all_pages_from_trash( + &state.pg_pool, + &state.collab_access_control_storage, + uid, + workspace_uuid, + ) + .await?; + Ok(Json(AppResponse::Ok())) +} + async fn update_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 30d9cf11..b6e1fd9a 100644 --- a/src/biz/workspace/page_view.rs +++ b/src/biz/workspace/page_view.rs @@ -164,6 +164,25 @@ async fn move_view_out_from_trash( }) } +async fn move_all_views_out_from_trash(folder: &mut Folder) -> Result { + let encoded_update = { + let mut txn = folder.collab.transact_mut(); + if let Some(op) = folder + .body + .section + .section_op(&txn, collab_folder::Section::Trash) + { + op.clear(&mut txn); + }; + 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 @@ -297,6 +316,29 @@ pub async fn restore_page_from_trash( Ok(()) } +pub async fn restore_all_pages_from_trash( + pg_pool: &PgPool, + collab_storage: &CollabAccessControlStorage, + uid: i64, + workspace_id: Uuid, +) -> 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 folder_update = move_all_views_out_from_trash(&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(()) +} + #[allow(clippy::too_many_arguments)] pub async fn update_page( pg_pool: &PgPool, diff --git a/tests/workspace/page_view.rs b/tests/workspace/page_view.rs index 2e3d9cfb..d4e92239 100644 --- a/tests/workspace/page_view.rs +++ b/tests/workspace/page_view.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{collections::HashSet, time::Duration}; use client_api::entity::{QueryCollab, QueryCollabParams}; use client_api_test::{ @@ -146,40 +146,49 @@ async fn move_page_to_trash() { .into_iter() .find(|v| v.name == "General") .unwrap(); - let view_id_to_be_deleted = general_space.children[0].view_id.clone(); + let view_ids_to_be_deleted = [ + general_space.children[0].view_id.clone(), + general_space.children[1].view_id.clone(), + ]; app_client.open_workspace_collab(&workspace_id).await; app_client .wait_object_sync_complete(&workspace_id) .await .unwrap(); - web_client - .api_client - .move_workspace_page_view_to_trash( - Uuid::parse_str(&workspace_id).unwrap(), - view_id_to_be_deleted.clone(), - ) - .await - .unwrap(); + for view_id in view_ids_to_be_deleted.iter() { + app_client + .api_client + .move_workspace_page_view_to_trash(Uuid::parse_str(&workspace_id).unwrap(), view_id.clone()) + .await + .unwrap(); + } let folder = get_latest_folder(&app_client, &workspace_id).await; - assert!(folder + let views_in_trash_for_app = folder .get_my_trash_sections() .iter() - .any(|v| v.id == view_id_to_be_deleted.clone())); - let view_found = web_client + .map(|v| v.id.clone()) + .collect::>(); + for view_id in view_ids_to_be_deleted.iter() { + assert!(views_in_trash_for_app.contains(view_id)); + } + let views_in_trash_for_web = 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()); - assert!(view_found); + .map(|v| v.view.view_id.clone()) + .collect::>(); + for view_id in view_ids_to_be_deleted.iter() { + assert!(views_in_trash_for_web.contains(view_id)); + } web_client .api_client .restore_workspace_page_view_from_trash( Uuid::parse_str(&workspace_id).unwrap(), - &view_id_to_be_deleted, + &view_ids_to_be_deleted[0], ) .await .unwrap(); @@ -187,7 +196,7 @@ async fn move_page_to_trash() { assert!(!folder .get_my_trash_sections() .iter() - .any(|v| v.id == view_id_to_be_deleted.clone())); + .any(|v| v.id == view_ids_to_be_deleted[0])); let view_found = web_client .api_client .get_workspace_trash(&workspace_id) @@ -195,7 +204,26 @@ async fn move_page_to_trash() { .unwrap() .views .iter() - .any(|v| v.view.view_id == view_id_to_be_deleted.clone()); + .any(|v| v.view.view_id == view_ids_to_be_deleted[0]); + assert!(!view_found); + web_client + .api_client + .restore_all_workspace_page_views_from_trash(Uuid::parse_str(&workspace_id).unwrap()) + .await + .unwrap(); + let folder = get_latest_folder(&app_client, &workspace_id).await; + assert!(!folder + .get_my_trash_sections() + .iter() + .any(|v| v.id == view_ids_to_be_deleted[1])); + let view_found = web_client + .api_client + .get_workspace_trash(&workspace_id) + .await + .unwrap() + .views + .iter() + .any(|v| v.view.view_id == view_ids_to_be_deleted[1]); assert!(!view_found); }