Merge pull request #931 from AppFlowy-IO/feat/custom-namespace-change-publish-name

feat: patching of publish name
This commit is contained in:
Zack 2024-10-28 10:52:57 +08:00 committed by GitHub
commit f96f4f779e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 244 additions and 32 deletions

View File

@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM public.af_workspace where workspace_id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "0eae8461a1caa6a609bfc4f329a4768ca0372a7b8cac54d83e3277ea0ad5ed9d"
}

View File

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE af_published_collab\n SET publish_name = $1\n WHERE workspace_id = $2\n AND view_id = $3\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Uuid",
"Uuid"
]
},
"nullable": []
},
"hash": "2ba7fdabdd71d2e3ac6dc1b67af17e8dcb59d13b644bc336da5351184ec4c9e1"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM public.af_workspace\n WHERE workspace_id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "f58a2f05efbda0698d27d83be5c6816fc46e3de33f926c6343bcbfa90a387b07"
}

View File

@ -144,7 +144,7 @@ tonic-build = "0.11.0"
log = "0.4.20"
lettre = { version = "0.11.7", features = ["tokio1", "tokio1-native-tls"] }
handlebars = "5.1.2"
pin-project = "1.1.5"
pin-project.workspace = true
byteorder = "1.5.0"
sha2 = "0.10.8"
rayon.workspace = true
@ -281,6 +281,7 @@ async_zip = { version = "0.0.17", features = ["full"] }
sanitize-filename = "0.5.0"
base64 = "0.22"
md5 = "0.7.0"
pin-project = "1.1.5"
# collaboration
yrs = { version = "0.21.2", features = ["sync"] }

View File

@ -357,6 +357,9 @@ pub enum ErrorCode {
CustomNamespaceDisabled = 1044,
CustomNamespaceDisallowed = 1045,
TooManyImportTask = 1046,
CustomNamespaceTooShort = 1047,
CustomNamespaceTooLong = 1048,
CustomNamespaceReserved = 1049,
}
impl ErrorCode {

View File

@ -45,7 +45,7 @@ collab-rt-entity = { workspace = true }
client-api-entity.workspace = true
serde_urlencoded = "0.7.1"
futures.workspace = true
pin-project = "1.1.5"
pin-project.workspace = true
percent-encoding = "2.3.1"
lazy_static = { workspace = true }
mime_guess = "2.0.5"

View File

@ -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<PublishInfo, AppResponseError> {
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::<PublishInfo>::from_response(resp)

View File

@ -1133,6 +1133,12 @@ pub struct PublishCollabItem<Meta, Data> {
pub data: Data,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PatchPublishedCollab {
pub view_id: Uuid,
pub publish_name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GlobalComments {
pub comments: Vec<GlobalComment>,

View File

@ -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,

View File

@ -19,16 +19,24 @@ use app_error::AppError;
#[inline]
pub async fn delete_from_workspace(pg_pool: &PgPool, workspace_id: &Uuid) -> Result<(), AppError> {
let pg_row = sqlx::query!(
let res = sqlx::query!(
r#"
DELETE FROM public.af_workspace where workspace_id = $1
DELETE FROM public.af_workspace
WHERE workspace_id = $1
"#,
workspace_id
)
.execute(pg_pool)
.await?;
debug_assert!(pg_row.rows_affected() == 1);
if res.rows_affected() != 1 {
tracing::error!(
"Failed to delete workspace, workspace_id: {}, rows_affected: {}",
workspace_id,
res.rows_affected()
);
}
Ok(())
}

View File

@ -13,9 +13,9 @@ serde_json.workspace = true
tracing.workspace = true
bytes = { workspace = true }
tokio = { workspace = true, optional = true }
pin-project = "1.1.6"
pin-project.workspace = true
futures = "0.3.30"
[features]
file_util = ["tokio/fs"]
request_util = ["reqwest"]
request_util = ["reqwest"]

View File

@ -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<Uuid>,
user_uuid: UserUuid,
state: Data<AppState>,
patches: Json<Vec<PatchPublishedCollab>>,
) -> Result<Json<AppResponse<()>>> {
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<Uuid>,
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()))
}

View File

@ -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<Vec<u8>, 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::<Vec<Uuid>>();
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(())
}

View File

@ -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};
@ -250,6 +251,56 @@ async fn test_publish_doc() {
assert_eq!(default_info_meta.meta.title, "my_title_1");
}
{
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::<MyCustomMetadata>(&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::<MyCustomMetadata>(&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::<MyCustomMetadata>(&my_namespace, publish_name_1)
.await
.unwrap();
}
// user unpublish collabs
c.unpublish_collabs(&workspace_id, &[view_id_1, view_id_2])
.await
.unwrap();