From 85452ddfabdb372bb3f9095d4e9cd40371a0008c Mon Sep 17 00:00:00 2001 From: Zack Date: Tue, 12 Nov 2024 21:12:58 +0800 Subject: [PATCH] feat: add role for user when querying workspaces (#983) --- ...c279f8a94aadcc29d0e2b652dc420506835e7.json | 29 ++++++++ libs/database-entity/src/dto.rs | 2 + libs/database/src/pg_row.rs | 2 + libs/database/src/workspace.rs | 30 +++++++-- libs/shared-entity/src/dto/workspace_dto.rs | 1 + src/api/workspace.rs | 8 ++- src/biz/workspace/ops.rs | 20 +++++- tests/workspace/invitation_crud.rs | 66 +++++++++++++++---- 8 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 .sqlx/query-aa75996ca6aa12f0bcaa5fb092ac279f8a94aadcc29d0e2b652dc420506835e7.json diff --git a/.sqlx/query-aa75996ca6aa12f0bcaa5fb092ac279f8a94aadcc29d0e2b652dc420506835e7.json b/.sqlx/query-aa75996ca6aa12f0bcaa5fb092ac279f8a94aadcc29d0e2b652dc420506835e7.json new file mode 100644 index 00000000..44339854 --- /dev/null +++ b/.sqlx/query-aa75996ca6aa12f0bcaa5fb092ac279f8a94aadcc29d0e2b652dc420506835e7.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT workspace_id, role_id\n FROM af_workspace_member\n WHERE workspace_id = ANY($1)\n AND uid = (SELECT uid FROM public.af_user WHERE uuid = $2)\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "role_id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "UuidArray", + "Uuid" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "aa75996ca6aa12f0bcaa5fb092ac279f8a94aadcc29d0e2b652dc420506835e7" +} diff --git a/libs/database-entity/src/dto.rs b/libs/database-entity/src/dto.rs index ea021bfd..873406e0 100644 --- a/libs/database-entity/src/dto.rs +++ b/libs/database-entity/src/dto.rs @@ -624,6 +624,8 @@ pub struct AFWorkspace { pub created_at: DateTime, pub icon: String, pub member_count: Option, + #[serde(default)] + pub role: Option, // role of the user requesting the workspace } #[derive(Serialize, Deserialize)] diff --git a/libs/database/src/pg_row.rs b/libs/database/src/pg_row.rs index 3d2c8812..7e456bf2 100644 --- a/libs/database/src/pg_row.rs +++ b/libs/database/src/pg_row.rs @@ -53,6 +53,7 @@ impl TryFrom for AFWorkspace { created_at, icon, member_count: None, + role: None, }) } } @@ -98,6 +99,7 @@ impl TryFrom for AFWorkspace { created_at, icon, member_count: Some(value.member_count), + role: None, }) } } diff --git a/libs/database/src/workspace.rs b/libs/database/src/workspace.rs index 93a7119a..6fe46e2d 100644 --- a/libs/database/src/workspace.rs +++ b/libs/database/src/workspace.rs @@ -851,10 +851,32 @@ pub async fn select_member_count_for_workspaces<'a, E: Executor<'a, Database = P }; ret.insert(row.workspace_id, count); } - for workspace_id in workspace_ids.iter() { - if !ret.contains_key(workspace_id) { - ret.insert(*workspace_id, 0); - } + + Ok(ret) +} + +pub async fn select_roles_for_workspaces( + pg_pool: &PgPool, + user_uuid: &Uuid, + workspace_ids: &[Uuid], +) -> Result, AppError> { + let query_res = sqlx::query!( + r#" + SELECT workspace_id, role_id + FROM af_workspace_member + WHERE workspace_id = ANY($1) + AND uid = (SELECT uid FROM public.af_user WHERE uuid = $2) + "#, + workspace_ids, + user_uuid, + ) + .fetch_all(pg_pool) + .await?; + + let mut ret = HashMap::with_capacity(workspace_ids.len()); + for row in query_res { + let role = AFRole::from(row.role_id); + ret.insert(row.workspace_id, role); } Ok(ret) diff --git a/libs/shared-entity/src/dto/workspace_dto.rs b/libs/shared-entity/src/dto/workspace_dto.rs index ec6b4ea2..af56fc2e 100644 --- a/libs/shared-entity/src/dto/workspace_dto.rs +++ b/libs/shared-entity/src/dto/workspace_dto.rs @@ -261,6 +261,7 @@ impl Default for ViewLayout { #[derive(Default, Debug, Deserialize, Serialize)] pub struct QueryWorkspaceParam { pub include_member_count: Option, + pub include_role: Option, } #[derive(Default, Debug, Deserialize, Serialize)] diff --git a/src/api/workspace.rs b/src/api/workspace.rs index 39114eed..be368858 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -338,10 +338,16 @@ async fn list_workspace_handler( state: Data, query: web::Query, ) -> Result>> { + let QueryWorkspaceParam { + include_member_count, + include_role, + } = query.into_inner(); + let workspaces = workspace::ops::get_all_user_workspaces( &state.pg_pool, &uuid, - query.into_inner().include_member_count.unwrap_or(false), + include_member_count.unwrap_or(false), + include_role.unwrap_or(false), ) .await?; Ok(AppResponse::Ok().with_data(workspaces).into()) diff --git a/src/biz/workspace/ops.rs b/src/biz/workspace/ops.rs index 67419b23..25a16ce2 100644 --- a/src/biz/workspace/ops.rs +++ b/src/biz/workspace/ops.rs @@ -256,6 +256,7 @@ pub async fn get_all_user_workspaces( pg_pool: &PgPool, user_uuid: &Uuid, include_member_count: bool, + include_role: bool, ) -> Result, AppResponseError> { let workspaces = select_all_user_workspaces(pg_pool, user_uuid).await?; let mut workspaces = workspaces @@ -273,10 +274,23 @@ pub async fn get_all_user_workspaces( .iter() .map(|row| row.workspace_id) .collect::>(); - let member_count_by_workspace_id = select_member_count_for_workspaces(pg_pool, &ids).await?; + let mut member_count_by_workspace_id = + select_member_count_for_workspaces(pg_pool, &ids).await?; for workspace in workspaces.iter_mut() { - if let Some(member_count) = member_count_by_workspace_id.get(&workspace.workspace_id) { - workspace.member_count = Some(*member_count); + if let Some(member_count) = member_count_by_workspace_id.remove(&workspace.workspace_id) { + workspace.member_count = Some(member_count); + } + } + } + if include_role { + let ids = workspaces + .iter() + .map(|row| row.workspace_id) + .collect::>(); + let mut roles_by_workspace_id = select_roles_for_workspaces(pg_pool, user_uuid, &ids).await?; + for workspace in workspaces.iter_mut() { + if let Some(role) = roles_by_workspace_id.remove(&workspace.workspace_id) { + workspace.role = Some(role.clone()); } } } diff --git a/tests/workspace/invitation_crud.rs b/tests/workspace/invitation_crud.rs index b258c47c..9964e689 100644 --- a/tests/workspace/invitation_crud.rs +++ b/tests/workspace/invitation_crud.rs @@ -15,6 +15,15 @@ async fn invite_workspace_crud() { .workspace_id; let (bob_client, bob) = generate_unique_registered_user_client().await; + let bob_workspace_id = bob_client + .get_workspaces() + .await + .unwrap() + .first() + .unwrap() + .workspace_id; + + // alice invite bob to alice's workspace alice_client .invite_workspace_members( alice_workspace_id.to_string().as_str(), @@ -94,16 +103,49 @@ async fn invite_workspace_crud() { .unwrap(); assert_eq!(accepted_invs.len(), 1); - // workspace now have 2 members - let member_count = alice_client - .get_workspaces_opt(QueryWorkspaceParam { - include_member_count: Some(true), - }) - .await - .unwrap() - .first() - .unwrap() - .member_count - .unwrap(); - assert_eq!(member_count, 2); + { + // alice's view of the workspaces + let workspaces = alice_client + .get_workspaces_opt(QueryWorkspaceParam { + include_member_count: Some(true), + include_role: Some(true), + }) + .await + .unwrap(); + + assert_eq!(workspaces.len(), 1); + assert_eq!(workspaces[0].workspace_id, alice_workspace_id); + assert_eq!(workspaces[0].member_count, Some(2)); + assert_eq!(workspaces[0].role, Some(AFRole::Owner)); + } + + { + // bob's view of the workspaces + // bob should see 2 workspaces, one is his own and the other is alice's + let workspaces = bob_client + .get_workspaces_opt(QueryWorkspaceParam { + include_member_count: Some(true), + include_role: Some(true), + }) + .await + .unwrap(); + assert_eq!(workspaces.len(), 2); + { + let alice_workspace = workspaces + .iter() + .find(|w| w.workspace_id == alice_workspace_id) + .unwrap(); + assert_eq!(alice_workspace.member_count, Some(2)); + assert_eq!(alice_workspace.role, Some(AFRole::Member)); + } + { + let bob_workspace = workspaces + .iter() + .find(|w| w.workspace_id == bob_workspace_id) + .unwrap(); + println!("{:?}", bob_workspace); + assert_eq!(bob_workspace.member_count, Some(1)); + assert_eq!(bob_workspace.role, Some(AFRole::Owner)); + } + } }