feat: api for restore page from trash

This commit is contained in:
khorshuheng 2024-11-08 10:44:58 +08:00
parent dd0cae4135
commit d04a0d8871
4 changed files with 151 additions and 62 deletions

View File

@ -40,10 +40,28 @@ impl Client {
AppResponse::<()>::from_response(resp).await?.into_error()
}
pub async fn restore_workspace_page_view_from_trash(
&self,
workspace_id: Uuid,
view_id: &str,
) -> Result<(), AppResponseError> {
let url = format!(
"{}/api/workspace/{}/page-view/{}/restore-from-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 update_workspace_page_view(
&self,
workspace_id: Uuid,
view_id: String,
view_id: &str,
params: &UpdatePageParams,
) -> Result<(), AppResponseError> {
let url = format!(
@ -62,7 +80,7 @@ impl Client {
pub async fn get_workspace_page_view(
&self,
workspace_id: Uuid,
view_id: Uuid,
view_id: &str,
) -> Result<PageCollab, AppResponseError> {
let url = format!(
"{}/api/workspace/{}/page-view/{}",

View File

@ -49,7 +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, update_page, update_page_collab_data,
create_page, get_page_view_collab, move_page_to_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::{
@ -138,6 +139,10 @@ pub fn workspace_scope() -> Scope {
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}/page-view/{view_id}/restore-from-trash")
.route(web::post().to(restore_page_from_trash_handler)),
)
.service(
web::resource("/{workspace_id}/batch/collab")
.route(web::post().to(batch_create_collab_handler)),
@ -918,6 +923,24 @@ async fn move_page_to_trash_handler(
Ok(Json(AppResponse::Ok()))
}
async fn restore_page_from_trash_handler(
user_uuid: UserUuid,
path: web::Path<(Uuid, String)>,
state: Data<AppState>,
) -> Result<Json<AppResponse<()>>> {
let uid = state.user_cache.get_user_uid(&user_uuid).await?;
let (workspace_uuid, view_id) = path.into_inner();
restore_page_from_trash(
&state.pg_pool,
&state.collab_access_control_storage,
uid,
workspace_uuid,
&view_id,
)
.await?;
Ok(Json(AppResponse::Ok()))
}
async fn update_page_view_handler(
user_uuid: UserUuid,
path: web::Path<(Uuid, String)>,

View File

@ -145,6 +145,25 @@ async fn move_view_to_trash(view_id: &str, folder: &mut Folder) -> Result<Folder
})
}
async fn move_view_out_from_trash(
view_id: &str,
folder: &mut Folder,
) -> Result<FolderUpdate, AppError> {
let encoded_update = {
let mut txn = folder.collab.transact_mut();
folder
.body
.views
.update_view(&mut txn, view_id, |update| update.set_trash(false).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<Vec<u8>, AppError> {
let collab_type = CollabType::Folder;
let encoded_folder_collab = folder
@ -254,6 +273,30 @@ pub async fn move_page_to_trash(
Ok(())
}
pub async fn restore_page_from_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 folder_update = move_view_out_from_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(())
}
#[allow(clippy::too_many_arguments)]
pub async fn update_page(
pg_pool: &PgPool,

View File

@ -14,6 +14,32 @@ use shared_entity::dto::workspace_dto::{
use tokio::time::sleep;
use uuid::Uuid;
async fn get_latest_folder(test_client: &TestClient, workspace_id: &str) -> Folder {
// Wait for websocket updates
sleep(Duration::from_secs(1)).await;
let lock = test_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 = test_client.uid().await;
Folder::from_collab_doc_state(
uid,
CollabOrigin::Client(CollabClient::new(uid, test_client.device_id.clone())),
encoded_collab.into(),
workspace_id,
vec![],
)
.unwrap()
}
#[tokio::test]
async fn get_page_view() {
let (c, _user) = generate_unique_registered_user_client().await;
@ -34,7 +60,7 @@ async fn get_page_view() {
.iter()
.find(|v| v.name == "To-dos")
.unwrap();
let todo_list_view_id = Uuid::parse_str(&todo.view_id).unwrap();
let todo_list_view_id = &todo.view_id;
let resp = c
.get_workspace_page_view(workspace_id, todo_list_view_id)
.await
@ -45,7 +71,7 @@ async fn get_page_view() {
.iter()
.find(|v| v.name == "Getting started")
.unwrap();
let getting_started_view_id = Uuid::parse_str(&getting_started.view_id).unwrap();
let getting_started_view_id = &getting_started.view_id;
let resp = c
.get_workspace_page_view(workspace_id, getting_started_view_id)
.await
@ -110,14 +136,9 @@ async fn move_page_to_trash() {
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)
.get_workspace_folder(&workspace_id, Some(2), None)
.await
.unwrap();
let general_space = &folder_view
@ -126,6 +147,11 @@ async fn move_page_to_trash() {
.find(|v| v.name == "General")
.unwrap();
let view_id_to_be_deleted = general_space.children[0].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(
@ -134,35 +160,12 @@ async fn move_page_to_trash() {
)
.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();
let folder = get_latest_folder(&app_client, &workspace_id).await;
assert!(folder
.get_my_trash_sections()
.iter()
.any(|v| v.id == view_id_to_be_deleted.clone()));
web_client
let view_found = web_client
.api_client
.get_workspace_trash(&workspace_id)
.await
@ -170,6 +173,30 @@ async fn move_page_to_trash() {
.views
.iter()
.any(|v| v.view.view_id == view_id_to_be_deleted.clone());
assert!(view_found);
web_client
.api_client
.restore_workspace_page_view_from_trash(
Uuid::parse_str(&workspace_id).unwrap(),
&view_id_to_be_deleted,
)
.await
.unwrap();
let folder = get_latest_folder(&app_client, &workspace_id).await;
assert!(!folder
.get_my_trash_sections()
.iter()
.any(|v| v.id == view_id_to_be_deleted.clone()));
let view_found = 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);
}
#[tokio::test]
@ -198,42 +225,20 @@ async fn update_page() {
.api_client
.update_workspace_page_view(
Uuid::parse_str(&workspace_id).unwrap(),
view_id_to_be_updated.clone(),
&view_id_to_be_updated,
&UpdatePageParams {
name: "New Name".to_string(),
icon: Some(ViewIcon {
ty: IconType::Emoji,
value: "🚀".to_string(),
}),
extra: Some(json!({"key": "value"})),
extra: Some(json!({"is_pinned": true})),
},
)
.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();
let folder = get_latest_folder(&app_client, &workspace_id).await;
let updated_view = folder.get_view(&view_id_to_be_updated).unwrap();
assert_eq!(updated_view.name, "New Name");
assert_eq!(
@ -245,6 +250,6 @@ async fn update_page() {
);
assert_eq!(
updated_view.extra,
Some(json!({"key": "value"}).to_string())
Some(json!({"is_pinned": true}).to_string())
);
}