From 5f220ab0b085987b9cc0e63bdee9d80b0ba33dd4 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Tue, 19 Mar 2024 21:58:56 +0800 Subject: [PATCH 1/6] feat: user leave workspace --- libs/database/src/user.rs | 16 ++++++++++++++++ src/api/workspace.rs | 18 ++++++++++++++++++ src/biz/collab/access_control.rs | 1 + src/biz/workspace/ops.rs | 10 ++++++++++ tests/workspace/member_crud.rs | 10 ++++++++++ 5 files changed, 55 insertions(+) diff --git a/libs/database/src/user.rs b/libs/database/src/user.rs index d66fc494..3fa75cc8 100644 --- a/libs/database/src/user.rs +++ b/libs/database/src/user.rs @@ -196,3 +196,19 @@ pub async fn is_user_exist<'a, E: Executor<'a, Database = Postgres>>( Ok(exists.unwrap_or(false)) } + +#[inline] +pub async fn select_email_from_user_uuid( + pool: &PgPool, + user_uuid: &Uuid, +) -> Result { + let email = sqlx::query_scalar!( + r#" + SELECT email FROM af_user WHERE uuid = $1 + "#, + user_uuid + ) + .fetch_one(pool) + .await?; + Ok(email) +} diff --git a/src/api/workspace.rs b/src/api/workspace.rs index 4ff2cb33..abac137d 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -74,6 +74,7 @@ pub fn workspace_scope() -> Scope { .route(web::delete().to(delete_workspace_handler)) ) .service(web::resource("/{workspace_id}/open").route(web::put().to(open_workspace_handler))) + .service(web::resource("/{workspace_id}/leave").route(web::put().to(leave_workspace_handler))) .service( web::resource("/{workspace_id}/member") .route(web::get().to(get_workspace_members_handler)) @@ -342,6 +343,23 @@ async fn open_workspace_handler( Ok(AppResponse::Ok().with_data(workspace).into()) } +#[instrument(level = "debug", skip_all, err)] +async fn leave_workspace_handler( + user_uuid: UserUuid, + state: Data, + workspace_id: web::Path, +) -> Result> { + let workspace_id = workspace_id.into_inner(); + workspace::ops::leave_workspace( + &state.pg_pool, + &workspace_id, + &user_uuid, + &state.workspace_access_control, + ) + .await?; + Ok(AppResponse::Ok().into()) +} + #[instrument(level = "debug", skip_all, err)] async fn update_workspace_member_handler( payload: Json, diff --git a/src/biz/collab/access_control.rs b/src/biz/collab/access_control.rs index 6d49851d..c8ac3f8c 100644 --- a/src/biz/collab/access_control.rs +++ b/src/biz/collab/access_control.rs @@ -43,6 +43,7 @@ pub trait CollabAccessControl: Sync + Send + 'static { async fn remove_access_level(&self, uid: &i64, oid: &str) -> Result<(), AppError>; } + #[derive(Clone)] pub struct CollabMiddlewareAccessControl { pub access_control: Arc, diff --git a/src/biz/workspace/ops.rs b/src/biz/workspace/ops.rs index c7e90a47..2b9b6d2b 100644 --- a/src/biz/workspace/ops.rs +++ b/src/biz/workspace/ops.rs @@ -315,6 +315,16 @@ pub async fn add_workspace_members_db_only( Ok(()) } +pub async fn leave_workspace( + pg_pool: &PgPool, + workspace_id: &Uuid, + user_uuid: &Uuid, + workspace_access_control: &impl WorkspaceAccessControl, +) -> Result<(), AppResponseError> { + let email = database::user::select_email_from_user_uuid(pg_pool, user_uuid).await?; + remove_workspace_members(pg_pool, workspace_id, &[email], workspace_access_control).await +} + pub async fn remove_workspace_members( pg_pool: &PgPool, workspace_id: &Uuid, diff --git a/tests/workspace/member_crud.rs b/tests/workspace/member_crud.rs index 2d318703..b62b7423 100644 --- a/tests/workspace/member_crud.rs +++ b/tests/workspace/member_crud.rs @@ -352,3 +352,13 @@ async fn get_user_workspace_info_after_open_workspace() { workspace_id_c1 ); } + +#[tokio::test] +async fn leave_workspace_test() { + let c1 = TestClient::new_user().await; + let workspace_id_c1 = c1.workspace_id().await; + + let c2 = TestClient::new_user().await; + c1.add_workspace_member(&workspace_id_c1, &c2, AFRole::Member) + .await; +} From 117473d1048ef401a27e60f9b26f7b9b08415376 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 20 Mar 2024 13:07:43 +0800 Subject: [PATCH 2/6] feat: leave workspace --- libs/client-api/src/http.rs | 12 ++++++++++++ src/api/workspace.rs | 2 +- tests/workspace/member_crud.rs | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libs/client-api/src/http.rs b/libs/client-api/src/http.rs index 20c1c6d1..5e03b4a1 100644 --- a/libs/client-api/src/http.rs +++ b/libs/client-api/src/http.rs @@ -585,6 +585,18 @@ impl Client { .into_data() } + #[instrument(level = "debug", skip_all, err)] + pub async fn leave_workspace(&self, workspace_id: &str) -> Result<(), AppResponseError> { + let url = format!("{}/api/workspace/{}/leave", self.base_url, workspace_id); + let resp = self + .http_client_with_auth(Method::POST, &url) + .await? + .send() + .await?; + log_request_id(&resp); + AppResponse::<()>::from_response(resp).await?.into_error() + } + #[instrument(level = "debug", skip_all, err)] pub async fn get_workspace_members>( &self, diff --git a/src/api/workspace.rs b/src/api/workspace.rs index abac137d..17dd51f9 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -74,7 +74,7 @@ pub fn workspace_scope() -> Scope { .route(web::delete().to(delete_workspace_handler)) ) .service(web::resource("/{workspace_id}/open").route(web::put().to(open_workspace_handler))) - .service(web::resource("/{workspace_id}/leave").route(web::put().to(leave_workspace_handler))) + .service(web::resource("/{workspace_id}/leave").route(web::post().to(leave_workspace_handler))) .service( web::resource("/{workspace_id}/member") .route(web::get().to(get_workspace_members_handler)) diff --git a/tests/workspace/member_crud.rs b/tests/workspace/member_crud.rs index b62b7423..9561fc24 100644 --- a/tests/workspace/member_crud.rs +++ b/tests/workspace/member_crud.rs @@ -1,4 +1,5 @@ use app_error::ErrorCode; +use client_api::entity::CreateCollabParams; use client_api_test_util::{api_client_with_email, TestClient}; use database_entity::dto::{AFAccessLevel, AFRole, QueryCollabMembers}; use shared_entity::dto::workspace_dto::WorkspaceMemberInvitation; @@ -361,4 +362,11 @@ async fn leave_workspace_test() { let c2 = TestClient::new_user().await; c1.add_workspace_member(&workspace_id_c1, &c2, AFRole::Member) .await; + c2.api_client + .leave_workspace(&workspace_id_c1) + .await + .unwrap(); + + let members = c1.get_workspace_members(&workspace_id_c1).await; + assert_eq!(members.len(), 1); } From a028f73dd3666e9bec586bba029726c04df331a9 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 20 Mar 2024 13:14:30 +0800 Subject: [PATCH 3/6] test: add test for owner removing itself from workspace --- tests/workspace/member_crud.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/workspace/member_crud.rs b/tests/workspace/member_crud.rs index 9561fc24..d234ade9 100644 --- a/tests/workspace/member_crud.rs +++ b/tests/workspace/member_crud.rs @@ -355,7 +355,7 @@ async fn get_user_workspace_info_after_open_workspace() { } #[tokio::test] -async fn leave_workspace_test() { +async fn member_leave_workspace_test() { let c1 = TestClient::new_user().await; let workspace_id_c1 = c1.workspace_id().await; @@ -370,3 +370,18 @@ async fn leave_workspace_test() { let members = c1.get_workspace_members(&workspace_id_c1).await; assert_eq!(members.len(), 1); } + +#[tokio::test] +async fn owner_leave_workspace_test() { + let c1 = TestClient::new_user().await; + let workspace_id_c1 = c1.workspace_id().await; + + let err = c1 + .api_client + .leave_workspace(&workspace_id_c1) + .await + .unwrap_err(); + + // owner of workspace cannot leave the workspace + assert_eq!(err.code, ErrorCode::NotEnoughPermissions); +} From 227e93c2ecd4c104888504209d017e086134cb46 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 20 Mar 2024 13:22:12 +0800 Subject: [PATCH 4/6] chore: sqlx offline files --- ...92c79131c175924f95e5d48d75931fb846f41.json | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .sqlx/query-bde2b88ffb1b59362c7ae82369892c79131c175924f95e5d48d75931fb846f41.json diff --git a/.sqlx/query-bde2b88ffb1b59362c7ae82369892c79131c175924f95e5d48d75931fb846f41.json b/.sqlx/query-bde2b88ffb1b59362c7ae82369892c79131c175924f95e5d48d75931fb846f41.json new file mode 100644 index 00000000..09567b35 --- /dev/null +++ b/.sqlx/query-bde2b88ffb1b59362c7ae82369892c79131c175924f95e5d48d75931fb846f41.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT email FROM af_user WHERE uuid = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false + ] + }, + "hash": "bde2b88ffb1b59362c7ae82369892c79131c175924f95e5d48d75931fb846f41" +} From 06192db59943d0823d9489303a163b9ad862cdb9 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 20 Mar 2024 13:27:37 +0800 Subject: [PATCH 5/6] chore: remove unused imports --- tests/workspace/member_crud.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/workspace/member_crud.rs b/tests/workspace/member_crud.rs index d234ade9..4655c929 100644 --- a/tests/workspace/member_crud.rs +++ b/tests/workspace/member_crud.rs @@ -1,5 +1,4 @@ use app_error::ErrorCode; -use client_api::entity::CreateCollabParams; use client_api_test_util::{api_client_with_email, TestClient}; use database_entity::dto::{AFAccessLevel, AFRole, QueryCollabMembers}; use shared_entity::dto::workspace_dto::WorkspaceMemberInvitation; From 52be8855ee64019017dd583a1875d6a0640458b6 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 20 Mar 2024 14:04:56 +0800 Subject: [PATCH 6/6] chore: add empty json payload --- libs/client-api/src/http.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/client-api/src/http.rs b/libs/client-api/src/http.rs index 5e03b4a1..0cc65004 100644 --- a/libs/client-api/src/http.rs +++ b/libs/client-api/src/http.rs @@ -591,6 +591,7 @@ impl Client { let resp = self .http_client_with_auth(Method::POST, &url) .await? + .json(&()) .send() .await?; log_request_id(&resp);