feat: handle duplicated publish names for a workspace

This commit is contained in:
Zack Fu Zi Xiang 2024-10-28 17:50:37 +08:00
parent 348a73685e
commit fdc889f73e
No known key found for this signature in database
4 changed files with 144 additions and 0 deletions

View File

@ -155,6 +155,12 @@ pub enum AppError {
#[error("There is existing access request for workspace {workspace_id} and view {view_id}")]
AccessRequestAlreadyExists { workspace_id: Uuid, view_id: Uuid },
#[error("There is existing published view for workspace {workspace_id} with publish_name {publish_name}")]
PublishNameAlreadyExists {
workspace_id: Uuid,
publish_name: String,
},
}
impl AppError {
@ -225,6 +231,7 @@ impl AppError {
AppError::MissingView(_) => ErrorCode::MissingView,
AppError::AccessRequestAlreadyExists { .. } => ErrorCode::AccessRequestAlreadyExists,
AppError::TooManyImportTask(_) => ErrorCode::TooManyImportTask,
AppError::PublishNameAlreadyExists { .. } => ErrorCode::PublishNameAlreadyExists,
}
}
}
@ -360,6 +367,7 @@ pub enum ErrorCode {
CustomNamespaceTooShort = 1047,
CustomNamespaceTooLong = 1048,
CustomNamespaceReserved = 1049,
PublishNameAlreadyExists = 1050,
}
impl ErrorCode {

View File

@ -1599,3 +1599,49 @@ pub async fn update_import_task_metadata(
Ok(())
}
#[inline]
pub async fn select_publish_name_exists(
pg_pool: &PgPool,
workspace_uuid: &Uuid,
publish_name: &str,
) -> Result<bool, AppError> {
let exists = sqlx::query_scalar!(
r#"
SELECT EXISTS(
SELECT 1
FROM af_published_collab
WHERE workspace_id = $1
AND publish_name = $2
)
"#,
workspace_uuid,
publish_name
)
.fetch_one(pg_pool)
.await?;
Ok(exists.unwrap_or(false))
}
#[inline]
pub async fn select_view_id_from_publish_name(
pg_pool: &PgPool,
workspace_uuid: &Uuid,
publish_name: &str,
) -> Result<Option<Uuid>, AppError> {
let res = sqlx::query_scalar!(
r#"
SELECT view_id
FROM af_published_collab
WHERE workspace_id = $1
AND publish_name = $2
"#,
workspace_uuid,
publish_name
)
.fetch_optional(pg_pool)
.await?;
Ok(res)
}

View File

@ -6,6 +6,7 @@ use database::{
select_default_published_view_id_for_namespace, update_published_collabs,
update_workspace_default_publish_view, update_workspace_default_publish_view_set_null,
},
workspace::{select_publish_name_exists, select_view_id_from_publish_name},
};
use database_entity::dto::PatchPublishedCollab;
use std::sync::Arc;
@ -291,6 +292,13 @@ impl PublishedCollabStore for PublishedCollabPostgresStore {
) -> Result<(), AppError> {
for publish_item in &publish_items {
check_collab_publish_name(publish_item.meta.publish_name.as_str())?;
check_view_id_publish_name_conflict(
&self.pg_pool,
workspace_id,
&publish_item.meta.view_id,
publish_item.meta.publish_name.as_str(),
)
.await?;
}
let publish_items_batch_size = publish_items.len() as i64;
let result =
@ -415,6 +423,14 @@ impl PublishedCollabStore for PublishedCollabS3StoreWithPostgresFallback {
let mut handles: Vec<tokio::task::JoinHandle<()>> = vec![];
for publish_item in &publish_items {
check_collab_publish_name(publish_item.meta.publish_name.as_str())?;
check_view_id_publish_name_conflict(
&self.pg_pool,
workspace_id,
&publish_item.meta.view_id,
publish_item.meta.publish_name.as_str(),
)
.await?;
let object_key = get_collab_s3_key(workspace_id, &publish_item.meta.view_id);
let data = publish_item.data.clone();
let bucket_client = self.bucket_client.clone();
@ -578,6 +594,7 @@ async fn patch_collabs(
for patch in patches {
if let Some(new_publish_name) = patch.publish_name.as_deref() {
check_collab_publish_name(new_publish_name)?;
check_publish_name_already_exists(pg_pool, workspace_id, new_publish_name).await?;
}
}
check_workspace_owner_or_publisher(pg_pool, user_uuid, workspace_id, &view_ids).await?;
@ -587,3 +604,41 @@ async fn patch_collabs(
txn.commit().await?;
Ok(())
}
/// Checks if the `publish_name` already exists for the workspace
async fn check_publish_name_already_exists(
pg_pool: &PgPool,
workspace_id: &Uuid,
publish_name: &str,
) -> Result<(), AppError> {
let publish_name_exists = select_publish_name_exists(pg_pool, workspace_id, publish_name).await?;
if publish_name_exists {
return Err(AppError::PublishNameAlreadyExists {
workspace_id: *workspace_id,
publish_name: publish_name.to_string(),
});
}
Ok(())
}
/// Check if the `publish_name` already exists on another view
async fn check_view_id_publish_name_conflict(
pg_pool: &PgPool,
workspace_id: &Uuid,
view_id: &Uuid,
publish_name: &str,
) -> Result<(), AppError> {
match select_view_id_from_publish_name(pg_pool, workspace_id, publish_name).await? {
Some(published_view_id) => {
if published_view_id != *view_id {
Err(AppError::PublishNameAlreadyExists {
workspace_id: *workspace_id,
publish_name: publish_name.to_string(),
})
} else {
Ok(())
}
},
None => Ok(()),
}
}

View File

@ -95,6 +95,7 @@ async fn test_publish_doc() {
let publish_name_2 = "publish-name-2";
let view_id_2 = uuid::Uuid::new_v4();
// User publishes two collabs
c.publish_collabs::<MyCustomMetadata, &[u8]>(
&workspace_id,
vec![
@ -123,6 +124,25 @@ async fn test_publish_doc() {
.await
.unwrap();
// User cannot publish another view_id with the same publish name
let err = c
.publish_collabs::<MyCustomMetadata, &[u8]>(
&workspace_id,
vec![PublishCollabItem {
meta: PublishCollabMetadata {
view_id: uuid::Uuid::new_v4(),
publish_name: publish_name_1.to_string(),
metadata: MyCustomMetadata {
title: "some_other_title".to_string(),
},
},
data: "some_other_yrs_data".as_bytes(),
}],
)
.await
.unwrap_err();
assert_eq!(err.code, ErrorCode::PublishNameAlreadyExists, "{:?}", err);
{
// Check that the published collabs are listed
let published_view_infos = c.list_published_views(&workspace_id).await.unwrap();
@ -263,6 +283,21 @@ async fn test_publish_doc() {
}
{
// User cannot change `publish_name` if the `publish_name` already exists
// for the same workspace
let err = c
.patch_published_collabs(
&workspace_id,
&[PatchPublishedCollab {
view_id: view_id_1,
// publish_name_2 already exists
publish_name: Some(publish_name_2.to_string()),
}],
)
.await
.unwrap_err();
assert_eq!(err.code, ErrorCode::PublishNameAlreadyExists, "{:?}", err);
let new_publish_name_1 = "new-publish-name-1".to_string();
// User change publish name