From e6dbc95641a3c24d90a30df52fcff82fd522d228 Mon Sep 17 00:00:00 2001 From: Khor Shu Heng <32997938+khorshuheng@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:32:29 +0800 Subject: [PATCH] feat: api to update space (#1009) --- libs/client-api/src/http_view.rs | 21 +++++- libs/shared-entity/src/dto/workspace_dto.rs | 8 +++ src/api/workspace.rs | 27 ++++++++ src/biz/workspace/page_view.rs | 77 ++++++++++++++++++++- tests/workspace/page_view.rs | 29 +++++++- 5 files changed, 156 insertions(+), 6 deletions(-) diff --git a/libs/client-api/src/http_view.rs b/libs/client-api/src/http_view.rs index a5b55cd6..61177a8d 100644 --- a/libs/client-api/src/http_view.rs +++ b/libs/client-api/src/http_view.rs @@ -1,5 +1,5 @@ use client_api_entity::workspace_dto::{ - CreatePageParams, CreateSpaceParams, Page, PageCollab, Space, UpdatePageParams, + CreatePageParams, CreateSpaceParams, Page, PageCollab, Space, UpdatePageParams, UpdateSpaceParams, }; use reqwest::Method; use serde_json::json; @@ -129,4 +129,23 @@ impl Client { .await?; AppResponse::::from_response(resp).await?.into_data() } + + pub async fn update_space( + &self, + workspace_id: Uuid, + view_id: &str, + params: &UpdateSpaceParams, + ) -> Result<(), AppResponseError> { + let url = format!( + "{}/api/workspace/{}/space/{}", + self.base_url, workspace_id, view_id + ); + let resp = self + .http_client_with_auth(Method::PATCH, &url) + .await? + .json(params) + .send() + .await?; + AppResponse::<()>::from_response(resp).await?.into_error() + } } diff --git a/libs/shared-entity/src/dto/workspace_dto.rs b/libs/shared-entity/src/dto/workspace_dto.rs index 63eae514..5671a11e 100644 --- a/libs/shared-entity/src/dto/workspace_dto.rs +++ b/libs/shared-entity/src/dto/workspace_dto.rs @@ -141,6 +141,14 @@ pub struct CreateSpaceParams { pub space_icon_color: String, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateSpaceParams { + pub space_permission: SpacePermission, + pub name: String, + pub space_icon: String, + pub space_icon_color: String, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreatePageParams { pub parent_view_id: String, diff --git a/src/api/workspace.rs b/src/api/workspace.rs index e8356e55..195cdd1a 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -51,6 +51,7 @@ use crate::biz::workspace::ops::{ use crate::biz::workspace::page_view::{ create_page, create_space, get_page_view_collab, move_page_to_trash, restore_all_pages_from_trash, restore_page_from_trash, update_page, update_page_collab_data, + update_space, }; use crate::biz::workspace::publish::get_workspace_default_publish_view_info_meta; use crate::domain::compression::{ @@ -128,6 +129,9 @@ pub fn workspace_scope() -> Scope { .route(web::post().to(post_web_update_handler)), ) .service(web::resource("/{workspace_id}/space").route(web::post().to(post_space_handler))) + .service( + web::resource("/{workspace_id}/space/{view_id}").route(web::patch().to(update_space_handler)), + ) .service( web::resource("/{workspace_id}/page-view").route(web::post().to(post_page_view_handler)), ) @@ -924,6 +928,29 @@ async fn post_space_handler( Ok(Json(AppResponse::Ok().with_data(space))) } +async fn update_space_handler( + user_uuid: UserUuid, + path: web::Path<(Uuid, String)>, + payload: Json, + state: Data, +) -> Result>> { + let uid = state.user_cache.get_user_uid(&user_uuid).await?; + let (workspace_uuid, view_id) = path.into_inner(); + update_space( + &state.pg_pool, + &state.collab_access_control_storage, + uid, + workspace_uuid, + &view_id, + &payload.space_permission, + &payload.name, + &payload.space_icon, + &payload.space_icon_color, + ) + .await?; + Ok(Json(AppResponse::Ok())) +} + async fn post_page_view_handler( user_uuid: UserUuid, path: web::Path, diff --git a/src/biz/workspace/page_view.rs b/src/biz/workspace/page_view.rs index ef61a202..c0c1a86f 100644 --- a/src/biz/workspace/page_view.rs +++ b/src/biz/workspace/page_view.rs @@ -9,7 +9,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::{CollabOrigin, Folder}; +use collab_folder::{timestamp, CollabOrigin, Folder}; use database::collab::{select_workspace_database_oid, CollabStorage, GetCollabOrigin}; use database::publish::select_published_view_ids_for_workspace; use database::user::select_web_user_from_uid; @@ -44,6 +44,43 @@ struct FolderUpdate { pub encoded_updates: Vec, } +#[allow(clippy::too_many_arguments)] +pub async fn update_space( + pg_pool: &PgPool, + collab_storage: &CollabAccessControlStorage, + uid: i64, + workspace_id: Uuid, + view_id: &str, + space_permission: &SpacePermission, + name: &str, + space_icon: &str, + space_icon_color: &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 = update_space_properties( + view_id, + &mut folder, + space_permission, + name, + space_icon, + space_icon_color, + ) + .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 create_space( pg_pool: &PgPool, @@ -145,7 +182,7 @@ async fn add_new_space_to_folder( space_permission: &SpacePermission, name: &str, space_icon: &str, - space_color: &str, + space_icon_color: &str, ) -> Result { let encoded_update = { let view = NestedChildViewBuilder::new(uid, workspace_id.to_string()) @@ -155,7 +192,7 @@ async fn add_new_space_to_folder( let mut extra = builder .is_space(true, to_space_permission(space_permission)) .build(); - extra["space_icon_color"] = json!(space_color); + extra["space_icon_color"] = json!(space_icon_color); extra["space_icon"] = json!(space_icon); extra }) @@ -177,6 +214,40 @@ async fn add_new_space_to_folder( }) } +async fn update_space_properties( + view_id: &str, + folder: &mut Folder, + space_permission: &SpacePermission, + name: &str, + space_icon: &str, + space_icon_color: &str, +) -> Result { + let encoded_update = { + let mut txn = folder.collab.transact_mut(); + folder.body.views.update_view(&mut txn, view_id, |update| { + let extra = json!({ + "is_space": true, + "space_permission": to_space_permission(space_permission) as u8, + "space_created_at": timestamp(), + "space_icon": space_icon, + "space_icon_color": space_icon_color, + }) + .to_string(); + let is_private = *space_permission == SpacePermission::Private; + update + .set_name(name) + .set_extra(&extra) + .set_private(is_private) + .done() + }); + txn.encode_update_v1() + }; + Ok(FolderUpdate { + updated_encoded_collab: folder_to_encoded_collab(folder)?, + encoded_updates: encoded_update, + }) +} + async fn add_new_view_to_folder( uid: i64, parent_view_id: &str, diff --git a/tests/workspace/page_view.rs b/tests/workspace/page_view.rs index 88734fed..07193d62 100644 --- a/tests/workspace/page_view.rs +++ b/tests/workspace/page_view.rs @@ -9,8 +9,8 @@ use collab_entity::CollabType; use collab_folder::{CollabOrigin, Folder}; use serde_json::{json, Value}; use shared_entity::dto::workspace_dto::{ - CreatePageParams, CreateSpaceParams, IconType, SpacePermission, UpdatePageParams, ViewIcon, - ViewLayout, + CreatePageParams, CreateSpaceParams, IconType, SpacePermission, UpdatePageParams, + UpdateSpaceParams, ViewIcon, ViewLayout, }; use tokio::time::sleep; use uuid::Uuid; @@ -352,4 +352,29 @@ async fn create_space() { .find(|v| v.name == "Private Space") .unwrap(); assert!(private_space.is_private); + + web_client + .api_client + .update_space( + workspace_uuid, + &private_space.view_id, + &UpdateSpaceParams { + space_permission: SpacePermission::PublicToAll, + name: "Renamed Space".to_string(), + space_icon: "space_icon_3".to_string(), + space_icon_color: "#000000".to_string(), + }, + ) + .await + .unwrap(); + let folder = get_latest_folder(&app_client, &workspace_id).await; + let view = folder.get_view(&private_space.view_id).unwrap(); + let space_info: Value = serde_json::from_str(view.extra.as_ref().unwrap()).unwrap(); + assert!(space_info["is_space"].as_bool().unwrap()); + assert_eq!( + space_info["space_permission"].as_u64().unwrap() as u8, + SpacePermission::PublicToAll as u8 + ); + assert_eq!(space_info["space_icon"].as_str().unwrap(), "space_icon_3"); + assert_eq!(space_info["space_icon_color"].as_str().unwrap(), "#000000"); }