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" +} diff --git a/libs/client-api/src/http.rs b/libs/client-api/src/http.rs index 20c1c6d1..0cc65004 100644 --- a/libs/client-api/src/http.rs +++ b/libs/client-api/src/http.rs @@ -585,6 +585,19 @@ 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? + .json(&()) + .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/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..17dd51f9 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::post().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..4655c929 100644 --- a/tests/workspace/member_crud.rs +++ b/tests/workspace/member_crud.rs @@ -352,3 +352,35 @@ async fn get_user_workspace_info_after_open_workspace() { workspace_id_c1 ); } + +#[tokio::test] +async fn member_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; + 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); +} + +#[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); +}