From b775aa9d4cf69e3796764e08273d7c5257349acd Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Thu, 24 Oct 2024 22:06:52 +0800 Subject: [PATCH] feat: patching of publish name --- libs/client-api/src/http_publish.rs | 21 ++++++++++- libs/database-entity/src/dto.rs | 6 +++ libs/database/src/publish.rs | 44 +++++++++++++++++++++- src/api/workspace.rs | 24 ++++++++++-- src/biz/workspace/publish.rs | 58 +++++++++++++++++++++++++++-- tests/workspace/publish.rs | 52 +++++++++++++++++++++++++- 6 files changed, 194 insertions(+), 11 deletions(-) diff --git a/libs/client-api/src/http_publish.rs b/libs/client-api/src/http_publish.rs index 13b318ef..e8d29247 100644 --- a/libs/client-api/src/http_publish.rs +++ b/libs/client-api/src/http_publish.rs @@ -3,7 +3,8 @@ use client_api_entity::workspace_dto::PublishInfoView; use client_api_entity::{workspace_dto::PublishedDuplicate, PublishInfo, UpdatePublishNamespace}; use client_api_entity::{ CreateGlobalCommentParams, CreateReactionParams, DeleteGlobalCommentParams, DeleteReactionParams, - GetReactionQueryParams, GlobalComments, PublishInfoMeta, Reactions, UpdateDefaultPublishView, + GetReactionQueryParams, GlobalComments, PatchPublishedCollab, PublishInfoMeta, Reactions, + UpdateDefaultPublishView, }; use reqwest::Method; use shared_entity::response::{AppResponse, AppResponseError}; @@ -75,6 +76,22 @@ impl Client { .into_data() } + pub async fn patch_published_collabs( + &self, + workspace_id: &str, + patches: &[PatchPublishedCollab], + ) -> Result<(), AppResponseError> { + let url = format!("{}/api/workspace/{}/publish", self.base_url, workspace_id); + let resp = self + .http_client_with_auth(Method::PATCH, &url) + .await? + .json(patches) + .send() + .await?; + log_request_id(&resp); + AppResponse::<()>::from_response(resp).await?.into_error() + } + pub async fn unpublish_collabs( &self, workspace_id: &str, @@ -251,7 +268,7 @@ impl Client { &self, view_id: &uuid::Uuid, ) -> Result { - let url = format!("{}/api/workspace/published-info/{}", self.base_url, view_id,); + let url = format!("{}/api/workspace/published-info/{}", self.base_url, view_id); let resp = self.cloud_client.get(&url).send().await?; AppResponse::::from_response(resp) diff --git a/libs/database-entity/src/dto.rs b/libs/database-entity/src/dto.rs index 00a5a67d..28badb73 100644 --- a/libs/database-entity/src/dto.rs +++ b/libs/database-entity/src/dto.rs @@ -1133,6 +1133,12 @@ pub struct PublishCollabItem { pub data: Data, } +#[derive(Debug, Serialize, Deserialize)] +pub struct PatchPublishedCollab { + pub view_id: Uuid, + pub publish_name: Option, +} + #[derive(Serialize, Deserialize, Debug)] pub struct GlobalComments { pub comments: Vec, diff --git a/libs/database/src/publish.rs b/libs/database/src/publish.rs index ffd77023..4f64d115 100644 --- a/libs/database/src/publish.rs +++ b/libs/database/src/publish.rs @@ -1,5 +1,7 @@ use app_error::AppError; -use database_entity::dto::{PublishCollabItem, PublishCollabKey, PublishInfo}; +use database_entity::dto::{ + PatchPublishedCollab, PublishCollabItem, PublishCollabKey, PublishInfo, +}; use sqlx::{Executor, PgPool, Postgres}; use uuid::Uuid; @@ -238,6 +240,46 @@ pub async fn delete_published_collabs<'a, E: Executor<'a, Database = Postgres>>( Ok(()) } +#[inline] +pub async fn update_published_collabs( + txn: &mut sqlx::Transaction<'_, Postgres>, + workspace_id: &Uuid, + patches: &[PatchPublishedCollab], +) -> Result<(), AppError> { + for patch in patches { + let new_publish_name = match &patch.publish_name { + Some(new_publish_name) => new_publish_name, + None => continue, + }; + + let res = sqlx::query!( + r#" + UPDATE af_published_collab + SET publish_name = $1 + WHERE workspace_id = $2 + AND view_id = $3 + "#, + patch.publish_name, + workspace_id, + patch.view_id, + ) + .execute(txn.as_mut()) + .await?; + + if res.rows_affected() != 1 { + tracing::error!( + "Failed to update published collab publish name, workspace_id: {}, view_id: {}, new_publish_name: {}, rows_affected: {}", + workspace_id, + patch.view_id, + new_publish_name, + res.rows_affected() + ); + } + } + + Ok(()) +} + #[inline] pub async fn select_published_metadata_for_view_id( pg_pool: &PgPool, diff --git a/src/api/workspace.rs b/src/api/workspace.rs index e7470ebf..d545c639 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -204,7 +204,8 @@ pub fn workspace_scope() -> Scope { .service( web::resource("/{workspace_id}/publish") .route(web::post().to(post_publish_collabs_handler)) - .route(web::delete().to(delete_published_collabs_handler)), + .route(web::delete().to(delete_published_collabs_handler)) + .route(web::patch().to(patch_published_collabs_handler)), ) .service( web::resource("/{workspace_id}/folder").route(web::get().to(get_workspace_folder_handler)), @@ -1440,6 +1441,23 @@ async fn post_publish_collabs_handler( Ok(Json(AppResponse::Ok())) } +async fn patch_published_collabs_handler( + workspace_id: web::Path, + user_uuid: UserUuid, + state: Data, + patches: Json>, +) -> Result>> { + let workspace_id = workspace_id.into_inner(); + if patches.is_empty() { + return Err(AppError::InvalidRequest("No patches provided".to_string()).into()); + } + state + .published_collab_store + .patch_collabs(&workspace_id, &user_uuid, &patches) + .await?; + Ok(Json(AppResponse::Ok())) +} + async fn delete_published_collabs_handler( workspace_id: web::Path, user_uuid: UserUuid, @@ -1449,11 +1467,11 @@ async fn delete_published_collabs_handler( let workspace_id = workspace_id.into_inner(); let view_ids = view_ids.into_inner(); if view_ids.is_empty() { - return Ok(Json(AppResponse::Ok())); + return Err(AppError::InvalidRequest("No view_ids provided".to_string()).into()); } state .published_collab_store - .delete_collab(&workspace_id, &view_ids, &user_uuid) + .delete_collabs(&workspace_id, &view_ids, &user_uuid) .await?; Ok(Json(AppResponse::Ok())) } diff --git a/src/biz/workspace/publish.rs b/src/biz/workspace/publish.rs index 12735cb3..3ba13fce 100644 --- a/src/biz/workspace/publish.rs +++ b/src/biz/workspace/publish.rs @@ -3,9 +3,11 @@ use database::{ collab::GetCollabOrigin, publish::{ select_all_published_collab_info, select_default_published_view_id, - select_default_published_view_id_for_namespace, update_workspace_default_publish_view, + select_default_published_view_id_for_namespace, update_published_collabs, + update_workspace_default_publish_view, }, }; +use database_entity::dto::PatchPublishedCollab; use std::sync::Arc; use app_error::AppError; @@ -251,12 +253,19 @@ pub trait PublishedCollabStore: Sync + Send + 'static { publish_name: &str, ) -> Result, AppError>; - async fn delete_collab( + async fn delete_collabs( &self, workspace_id: &Uuid, view_ids: &[Uuid], user_uuid: &Uuid, ) -> Result<(), AppError>; + + async fn patch_collabs( + &self, + workspace_id: &Uuid, + user_uuid: &Uuid, + patches: &[PatchPublishedCollab], + ) -> Result<(), AppError>; } pub struct PublishedCollabPostgresStore { @@ -351,7 +360,7 @@ impl PublishedCollabStore for PublishedCollabPostgresStore { result } - async fn delete_collab( + async fn delete_collabs( &self, workspace_id: &Uuid, view_ids: &[Uuid], @@ -361,6 +370,15 @@ impl PublishedCollabStore for PublishedCollabPostgresStore { delete_published_collabs(&self.pg_pool, workspace_id, view_ids).await?; Ok(()) } + + async fn patch_collabs( + &self, + workspace_id: &Uuid, + user_uuid: &Uuid, + patches: &[PatchPublishedCollab], + ) -> Result<(), AppError> { + patch_collabs(&self.pg_pool, workspace_id, user_uuid, patches).await + } } pub struct PublishedCollabS3StoreWithPostgresFallback { @@ -519,7 +537,7 @@ impl PublishedCollabStore for PublishedCollabS3StoreWithPostgresFallback { } } - async fn delete_collab( + async fn delete_collabs( &self, workspace_id: &Uuid, view_ids: &[Uuid], @@ -534,4 +552,36 @@ impl PublishedCollabStore for PublishedCollabS3StoreWithPostgresFallback { delete_published_collabs(&self.pg_pool, workspace_id, view_ids).await?; Ok(()) } + + async fn patch_collabs( + &self, + workspace_id: &Uuid, + user_uuid: &Uuid, + patches: &[PatchPublishedCollab], + ) -> Result<(), AppError> { + patch_collabs(&self.pg_pool, workspace_id, user_uuid, patches).await + } +} + +async fn patch_collabs( + pg_pool: &PgPool, + workspace_id: &Uuid, + user_uuid: &Uuid, + patches: &[PatchPublishedCollab], +) -> Result<(), AppError> { + let view_ids = patches + .iter() + .map(|patch| patch.view_id) + .collect::>(); + for patch in patches { + if let Some(new_publish_name) = patch.publish_name.as_deref() { + check_collab_publish_name(new_publish_name)?; + } + } + check_workspace_owner_or_publisher(pg_pool, user_uuid, workspace_id, &view_ids).await?; + + let mut txn = pg_pool.begin().await?; + update_published_collabs(&mut txn, workspace_id, patches).await?; + txn.commit().await?; + Ok(()) } diff --git a/tests/workspace/publish.rs b/tests/workspace/publish.rs index ec20dc94..79b3b54a 100644 --- a/tests/workspace/publish.rs +++ b/tests/workspace/publish.rs @@ -2,7 +2,8 @@ use app_error::ErrorCode; use appflowy_cloud::biz::collab::folder_view::collab_folder_to_folder_view; use appflowy_cloud::biz::workspace::ops::collab_from_doc_state; use client_api::entity::{ - AFRole, GlobalComment, PublishCollabItem, PublishCollabMetadata, PublishInfoMeta, + AFRole, GlobalComment, PatchPublishedCollab, PublishCollabItem, PublishCollabMetadata, + PublishInfoMeta, }; use client_api_test::TestClient; use client_api_test::{generate_unique_registered_user_client, localhost_client}; @@ -255,6 +256,55 @@ async fn test_publish_doc() { .await .unwrap(); + { + let new_publish_name_1 = "new-publish-name-1".to_string(); + + // User change publish name + c.patch_published_collabs( + &workspace_id, + &[PatchPublishedCollab { + view_id: view_id_1, + publish_name: Some(new_publish_name_1.to_string()), + }], + ) + .await + .unwrap(); + + // Guest now cannot access the collab using old publish name + let guest_client = localhost_client(); + let err = guest_client + .get_published_collab::(&my_namespace, publish_name_1) + .await + .err() + .unwrap(); + assert_eq!(err.code, ErrorCode::RecordNotFound, "{:?}", err); + + // Guest now access the collab using new publish name + let guest_client = localhost_client(); + let _ = guest_client + .get_published_collab::(&my_namespace, &new_publish_name_1) + .await + .unwrap(); + + // Switch back to old publish name + c.patch_published_collabs( + &workspace_id, + &[PatchPublishedCollab { + view_id: view_id_1, + publish_name: Some(publish_name_1.to_string()), + }], + ) + .await + .unwrap(); + + // Guest can access the collab using the orginal publish name + let guest_client = localhost_client(); + let _ = guest_client + .get_published_collab::(&my_namespace, publish_name_1) + .await + .unwrap(); + } + { // Deleted collab should not be accessible let guest_client = localhost_client();