From b525e333f022c50cdfa8d2ad5e5e352cb3af53b4 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Thu, 20 Jun 2024 12:05:12 +0800 Subject: [PATCH] feat: batch unpublish --- ...f6a16f89a5160d1095e1906985b9ec2123ec.json} | 6 +-- ...9cbf4598c36204a6885b2e279c848cedcfa75.json | 24 +++++++++++ ...24fedd32b19b456f2a865860cad597903fa5b.json | 16 -------- ...c034e89a684d739333653bea51a402226076e.json | 19 --------- ...1b10ef73fd1f87f94ce4f2e9f32136a1b4f67.json | 24 ----------- ...d65fcd003c6a645876f7aa8e677eaad59517c.json | 20 ++++++++++ libs/client-api/src/http_publish.rs | 10 ++--- libs/database/src/workspace.rs | 40 ++++++++++--------- src/api/workspace.rs | 26 +++++++----- src/biz/workspace/ops.rs | 15 +++---- tests/workspace/publish.rs | 2 +- 11 files changed, 96 insertions(+), 106 deletions(-) rename .sqlx/{query-cb7c6555f1c837748cf0c7706000b28026c3dcdb7305f8c3a49082e1067f1fd8.json => query-02eab22805a4cba99dc7fc554f63f6a16f89a5160d1095e1906985b9ec2123ec.json} (58%) create mode 100644 .sqlx/query-87628d6739441a22229d08832d09cbf4598c36204a6885b2e279c848cedcfa75.json delete mode 100644 .sqlx/query-c51b2030045bd58b1b3ac2ed12c24fedd32b19b456f2a865860cad597903fa5b.json delete mode 100644 .sqlx/query-d979dc9b4db36e59e4eda6b9643c034e89a684d739333653bea51a402226076e.json delete mode 100644 .sqlx/query-dd6ff315461ec898e43bc4624b51b10ef73fd1f87f94ce4f2e9f32136a1b4f67.json create mode 100644 .sqlx/query-fa640accaba3655cd842ebc4838d65fcd003c6a645876f7aa8e677eaad59517c.json diff --git a/.sqlx/query-cb7c6555f1c837748cf0c7706000b28026c3dcdb7305f8c3a49082e1067f1fd8.json b/.sqlx/query-02eab22805a4cba99dc7fc554f63f6a16f89a5160d1095e1906985b9ec2123ec.json similarity index 58% rename from .sqlx/query-cb7c6555f1c837748cf0c7706000b28026c3dcdb7305f8c3a49082e1067f1fd8.json rename to .sqlx/query-02eab22805a4cba99dc7fc554f63f6a16f89a5160d1095e1906985b9ec2123ec.json index 8860b44b..c9311d12 100644 --- a/.sqlx/query-cb7c6555f1c837748cf0c7706000b28026c3dcdb7305f8c3a49082e1067f1fd8.json +++ b/.sqlx/query-02eab22805a4cba99dc7fc554f63f6a16f89a5160d1095e1906985b9ec2123ec.json @@ -1,15 +1,15 @@ { "db_name": "PostgreSQL", - "query": "\n DELETE FROM af_published_collab\n WHERE workspace_id = $1 AND view_id = $2\n ", + "query": "\n DELETE FROM af_published_collab\n WHERE workspace_id = $1\n AND view_id = ANY($2)\n ", "describe": { "columns": [], "parameters": { "Left": [ "Uuid", - "Uuid" + "UuidArray" ] }, "nullable": [] }, - "hash": "cb7c6555f1c837748cf0c7706000b28026c3dcdb7305f8c3a49082e1067f1fd8" + "hash": "02eab22805a4cba99dc7fc554f63f6a16f89a5160d1095e1906985b9ec2123ec" } diff --git a/.sqlx/query-87628d6739441a22229d08832d09cbf4598c36204a6885b2e279c848cedcfa75.json b/.sqlx/query-87628d6739441a22229d08832d09cbf4598c36204a6885b2e279c848cedcfa75.json new file mode 100644 index 00000000..95adc4ea --- /dev/null +++ b/.sqlx/query-87628d6739441a22229d08832d09cbf4598c36204a6885b2e279c848cedcfa75.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT COUNT(*)\n FROM af_published_collab\n WHERE workspace_id = $1\n AND view_id = ANY($2)\n AND published_by = (SELECT uid FROM af_user WHERE uuid = $3)\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Uuid", + "UuidArray", + "Uuid" + ] + }, + "nullable": [ + null + ] + }, + "hash": "87628d6739441a22229d08832d09cbf4598c36204a6885b2e279c848cedcfa75" +} diff --git a/.sqlx/query-c51b2030045bd58b1b3ac2ed12c24fedd32b19b456f2a865860cad597903fa5b.json b/.sqlx/query-c51b2030045bd58b1b3ac2ed12c24fedd32b19b456f2a865860cad597903fa5b.json deleted file mode 100644 index 7a220ee3..00000000 --- a/.sqlx/query-c51b2030045bd58b1b3ac2ed12c24fedd32b19b456f2a865860cad597903fa5b.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE af_published_collab\n SET blob = $1\n WHERE workspace_id = $2\n AND view_id = $3\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Bytea", - "Uuid", - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "c51b2030045bd58b1b3ac2ed12c24fedd32b19b456f2a865860cad597903fa5b" -} diff --git a/.sqlx/query-d979dc9b4db36e59e4eda6b9643c034e89a684d739333653bea51a402226076e.json b/.sqlx/query-d979dc9b4db36e59e4eda6b9643c034e89a684d739333653bea51a402226076e.json deleted file mode 100644 index 211f8cf3..00000000 --- a/.sqlx/query-d979dc9b4db36e59e4eda6b9643c034e89a684d739333653bea51a402226076e.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO af_published_collab (workspace_id, view_id, doc_name, published_by, metadata)\n SELECT * FROM UNNEST(\n (SELECT array_agg((SELECT $1::uuid)) FROM generate_series(1, $6))::uuid[],\n $2::uuid[],\n $3::text[],\n (SELECT array_agg((SELECT uid FROM af_user WHERE uuid = $4)) FROM generate_series(1, $6))::bigint[],\n $5::jsonb[]\n )\n ON CONFLICT (workspace_id, view_id) DO UPDATE\n SET metadata = EXCLUDED.metadata\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "UuidArray", - "TextArray", - "Uuid", - "JsonbArray", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "d979dc9b4db36e59e4eda6b9643c034e89a684d739333653bea51a402226076e" -} diff --git a/.sqlx/query-dd6ff315461ec898e43bc4624b51b10ef73fd1f87f94ce4f2e9f32136a1b4f67.json b/.sqlx/query-dd6ff315461ec898e43bc4624b51b10ef73fd1f87f94ce4f2e9f32136a1b4f67.json deleted file mode 100644 index d92690c2..00000000 --- a/.sqlx/query-dd6ff315461ec898e43bc4624b51b10ef73fd1f87f94ce4f2e9f32136a1b4f67.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT EXISTS(\n SELECT 1\n FROM af_published_collab\n WHERE workspace_id = $1\n AND view_id = $2\n AND published_by = (SELECT uid FROM af_user WHERE uuid = $3)\n );\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Uuid", - "Uuid" - ] - }, - "nullable": [ - null - ] - }, - "hash": "dd6ff315461ec898e43bc4624b51b10ef73fd1f87f94ce4f2e9f32136a1b4f67" -} diff --git a/.sqlx/query-fa640accaba3655cd842ebc4838d65fcd003c6a645876f7aa8e677eaad59517c.json b/.sqlx/query-fa640accaba3655cd842ebc4838d65fcd003c6a645876f7aa8e677eaad59517c.json new file mode 100644 index 00000000..62b8326b --- /dev/null +++ b/.sqlx/query-fa640accaba3655cd842ebc4838d65fcd003c6a645876f7aa8e677eaad59517c.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO af_published_collab (workspace_id, view_id, doc_name, published_by, metadata, blob)\n SELECT * FROM UNNEST(\n (SELECT array_agg((SELECT $1::uuid)) FROM generate_series(1, $7))::uuid[],\n $2::uuid[],\n $3::text[],\n (SELECT array_agg((SELECT uid FROM af_user WHERE uuid = $4)) FROM generate_series(1, $7))::bigint[],\n $5::jsonb[],\n $6::bytea[]\n )\n ON CONFLICT (workspace_id, view_id) DO UPDATE\n SET metadata = EXCLUDED.metadata\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Uuid", + "UuidArray", + "TextArray", + "Uuid", + "JsonbArray", + "ByteaArray", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "fa640accaba3655cd842ebc4838d65fcd003c6a645876f7aa8e677eaad59517c" +} diff --git a/libs/client-api/src/http_publish.rs b/libs/client-api/src/http_publish.rs index 51bae299..2c5dbf82 100644 --- a/libs/client-api/src/http_publish.rs +++ b/libs/client-api/src/http_publish.rs @@ -76,18 +76,16 @@ impl Client { AppResponse::<()>::from_response(resp).await?.into_error() } - pub async fn delete_published_collab( + pub async fn unpublish_collabs( &self, workspace_id: &str, - view_id: &uuid::Uuid, + view_ids: &[uuid::Uuid], ) -> Result<(), AppResponseError> { - let url = format!( - "{}/api/workspace/{}/publish/{}", - self.base_url, workspace_id, view_id - ); + let url = format!("{}/api/workspace/{}/publish", self.base_url, workspace_id); let resp = self .http_client_with_auth(Method::DELETE, &url) .await? + .json(view_ids) .send() .await?; AppResponse::<()>::from_response(resp).await?.into_error() diff --git a/libs/database/src/workspace.rs b/libs/database/src/workspace.rs index 0c93bb82..084fb5ba 100644 --- a/libs/database/src/workspace.rs +++ b/libs/database/src/workspace.rs @@ -144,30 +144,31 @@ pub async fn select_user_is_workspace_owner( Ok(exists.unwrap_or(false)) } -pub async fn select_user_is_collab_publisher( +pub async fn select_user_is_collab_publisher_for_all_views( pg_pool: &PgPool, user_uuid: &Uuid, workspace_uuid: &Uuid, - view_id: &Uuid, + view_ids: &[Uuid], ) -> Result { - let exists = sqlx::query_scalar!( + let count = sqlx::query_scalar!( r#" - SELECT EXISTS( - SELECT 1 - FROM af_published_collab - WHERE workspace_id = $1 - AND view_id = $2 - AND published_by = (SELECT uid FROM af_user WHERE uuid = $3) - ); + SELECT COUNT(*) + FROM af_published_collab + WHERE workspace_id = $1 + AND view_id = ANY($2) + AND published_by = (SELECT uid FROM af_user WHERE uuid = $3) "#, workspace_uuid, - view_id, + view_ids, user_uuid, ) .fetch_one(pg_pool) .await?; - Ok(exists.unwrap_or(false)) + match count { + Some(c) => Ok(c == view_ids.len() as i64), + None => Ok(false), + } } #[inline] @@ -963,27 +964,28 @@ pub async fn select_publish_collab_meta<'a, E: Executor<'a, Database = Postgres> } #[inline] -pub async fn delete_published_collab<'a, E: Executor<'a, Database = Postgres>>( +pub async fn delete_published_collabs<'a, E: Executor<'a, Database = Postgres>>( executor: E, workspace_id: &Uuid, - view_id: &Uuid, + view_ids: &[Uuid], ) -> Result<(), AppError> { let res = sqlx::query!( r#" DELETE FROM af_published_collab - WHERE workspace_id = $1 AND view_id = $2 + WHERE workspace_id = $1 + AND view_id = ANY($2) "#, workspace_id, - view_id, + view_ids, ) .execute(executor) .await?; - if res.rows_affected() != 1 { + if res.rows_affected() != view_ids.len() as u64 { tracing::error!( - "Failed to delete published collab, workspace_id: {}, view_id: {}, rows_affected: {}", + "Failed to delete published collabs, workspace_id: {}, view_ids: {:?}, rows_affected: {}", workspace_id, - view_id, + view_ids, res.rows_affected() ); } diff --git a/src/api/workspace.rs b/src/api/workspace.rs index 78bab304..6360bc4d 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -148,11 +148,8 @@ pub fn workspace_scope() -> Scope { ) .service( web::resource("/{workspace_id}/publish") - .route(web::post().to(post_publish_collab_handler)) - ) - .service( - web::resource("/{workspace_id}/publish/{view_id}") - .route(web::delete().to(delete_publish_collab_handler)) + .route(web::post().to(post_publish_collabs_handler)) + .route(web::delete().to(delete_published_collabs_handler)) ) .service( web::resource("/{workspace_id}/collab/{object_id}/member/list") @@ -1003,7 +1000,7 @@ async fn get_published_collab_info_handler( Ok(Json(AppResponse::Ok().with_data(collab_data))) } -async fn post_publish_collab_handler( +async fn post_publish_collabs_handler( workspace_id: web::Path, user_uuid: UserUuid, mut payload: Payload, @@ -1015,7 +1012,6 @@ async fn post_publish_collab_handler( let mut accumulator = Vec::>>::new(); while let Some(item) = payload.try_next().await? { - println!("Publishing collab item: {:#?}", item); let item_len = item.len(); let mut cursor = Cursor::new(item); @@ -1044,21 +1040,29 @@ async fn post_publish_collab_handler( ); } + if accumulator.is_empty() { + return Ok(Json(AppResponse::Ok())); + } biz::workspace::ops::publish_collabs(&state.pg_pool, &workspace_id, &user_uuid, &accumulator) .await?; Ok(Json(AppResponse::Ok())) } -async fn delete_publish_collab_handler( - path_param: web::Path<(Uuid, Uuid)>, +async fn delete_published_collabs_handler( + workspace_id: web::Path, user_uuid: UserUuid, state: Data, + view_ids: Json>, ) -> Result>> { - let (workspace_id, view_id) = path_param.into_inner(); + let workspace_id = workspace_id.into_inner(); + let view_ids = view_ids.into_inner(); + if view_ids.is_empty() { + return Ok(Json(AppResponse::Ok())); + } biz::workspace::ops::delete_published_workspace_collab( &state.pg_pool, &workspace_id, - &view_id, + &view_ids, &user_uuid, ) .await?; diff --git a/src/biz/workspace/ops.rs b/src/biz/workspace/ops.rs index 5449428a..5caa060c 100644 --- a/src/biz/workspace/ops.rs +++ b/src/biz/workspace/ops.rs @@ -19,11 +19,11 @@ use database::pg_row::{AFWorkspaceMemberRow, AFWorkspaceRow}; use database::user::select_uid_from_email; use database::workspace::{ - change_workspace_icon, delete_from_workspace, delete_published_collab, delete_workspace_members, + change_workspace_icon, delete_from_workspace, delete_published_collabs, delete_workspace_members, get_invitation_by_id, insert_or_replace_publish_collab_metas, insert_user_workspace, insert_workspace_invitation, rename_workspace, select_all_user_workspaces, select_publish_collab_meta, select_published_collab_blob, select_published_collab_info, - select_user_is_collab_publisher, select_user_is_workspace_owner, select_workspace, + select_user_is_collab_publisher_for_all_views, select_user_is_workspace_owner, select_workspace, select_workspace_invitations_for_user, select_workspace_member, select_workspace_member_list, select_workspace_publish_namespace, select_workspace_publish_namespace_exists, select_workspace_settings, select_workspace_total_collab_bytes, update_updated_at_of_workspace, @@ -184,11 +184,11 @@ pub async fn get_published_collab_info( pub async fn delete_published_workspace_collab( pg_pool: &PgPool, workspace_id: &Uuid, - view_id: &Uuid, + view_ids: &[Uuid], user_uuid: &Uuid, ) -> Result<(), AppError> { - check_workspace_owner_or_publisher(pg_pool, user_uuid, workspace_id, view_id).await?; - delete_published_collab(pg_pool, workspace_id, view_id).await?; + check_workspace_owner_or_publisher(pg_pool, user_uuid, workspace_id, view_ids).await?; + delete_published_collabs(pg_pool, workspace_id, view_ids).await?; Ok(()) } @@ -573,12 +573,13 @@ async fn check_workspace_owner_or_publisher( pg_pool: &PgPool, user_uuid: &Uuid, workspace_id: &Uuid, - view_id: &Uuid, + view_id: &[Uuid], ) -> Result<(), AppError> { let is_owner = select_user_is_workspace_owner(pg_pool, user_uuid, workspace_id).await?; if !is_owner { let is_publisher = - select_user_is_collab_publisher(pg_pool, user_uuid, workspace_id, view_id).await?; + select_user_is_collab_publisher_for_all_views(pg_pool, user_uuid, workspace_id, view_id) + .await?; if !is_publisher { return Err(AppError::UserUnAuthorized( "User is not the owner of the workspace or the publisher of the document".to_string(), diff --git a/tests/workspace/publish.rs b/tests/workspace/publish.rs index 7393818c..3914f163 100644 --- a/tests/workspace/publish.rs +++ b/tests/workspace/publish.rs @@ -129,7 +129,7 @@ async fn test_publish_doc() { assert_eq!(blob, "yrs_encoded_data_1"); } - c.delete_published_collab(&workspace_id, &view_id_1) + c.unpublish_collabs(&workspace_id, &[view_id_1]) .await .unwrap();