diff --git a/imphnen-iam/src/v1/permissions/permissions_enum.rs b/imphnen-iam/src/v1/permissions/permissions_enum.rs index 0315cdb..10a8c10 100644 --- a/imphnen-iam/src/v1/permissions/permissions_enum.rs +++ b/imphnen-iam/src/v1/permissions/permissions_enum.rs @@ -30,6 +30,14 @@ pub enum PermissionsEnum { ReadListTeams, ReadDetailTeams, + // Administrator permissions + ManageAllUsers, + ManageAllRoles, + ManageAllPermissions, + ManageAllTeams, + ViewAllSensitiveData, + AccessAdminDashboard, + // Gacha permissions CreateGachaClaims, ReadDetailGachaClaims, diff --git a/imphnen-iam/src/v1/teams/admin_teams_controller.rs b/imphnen-iam/src/v1/teams/admin_teams_controller.rs index 9e4d9db..6f35123 100644 --- a/imphnen-iam/src/v1/teams/admin_teams_controller.rs +++ b/imphnen-iam/src/v1/teams/admin_teams_controller.rs @@ -2,8 +2,7 @@ use crate::{AppState, MetaRequestDto}; use crate::{ MessageResponseDto, ResponseListSuccessDto, ResponseSuccessDto, TeamsCreateRequestDto, TeamsUpdateRequestDto, TeamInviteRequestDto, - TeamAcceptInvitationRequestDto, TeamMemberDto, TeamsSearchQueryDto, - AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum + TeamMemberDto, AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum }; use axum::response::Response; use axum::extract::Path; diff --git a/imphnen-iam/src/v1/teams/mod.rs b/imphnen-iam/src/v1/teams/mod.rs index 4dd2dda..db61d8e 100644 --- a/imphnen-iam/src/v1/teams/mod.rs +++ b/imphnen-iam/src/v1/teams/mod.rs @@ -5,16 +5,14 @@ pub mod teams_repository; pub mod teams_schema; pub mod teams_service; -pub use admin_teams_controller::*; -pub use teams_controller::*; +pub use admin_teams_controller::{admin_teams_router, get_all_teams as admin_get_all_teams, get_team_by_id as admin_get_team_by_id, get_team_members as admin_get_team_members, create_team as admin_create_team, update_team as admin_update_team, delete_team as admin_delete_team, invite_team_members as admin_invite_team_members}; +pub use teams_controller::{teams_router as user_teams_router, get_team_list, get_team_by_id as user_get_team_by_id, get_team_members as user_get_team_members}; pub use teams_dto::*; pub use teams_repository::*; pub use teams_schema::*; pub use teams_service::*; use axum::Router; -use crate::teams_controller::*; -use crate::admin_teams_controller::*; pub fn teams_router() -> Router { Router::new() diff --git a/imphnen-iam/src/v1/teams/teams_controller.rs b/imphnen-iam/src/v1/teams/teams_controller.rs index df82297..0547a34 100644 --- a/imphnen-iam/src/v1/teams/teams_controller.rs +++ b/imphnen-iam/src/v1/teams/teams_controller.rs @@ -48,70 +48,50 @@ where } #[utoipa::path( - get, - security( - ("Bearer" = []) - ), - path = "/v1/teams", - params( - ("page" = Option, Query, description = "Page number"), - ("per_page" = Option, Query, description = "Items per page"), - ("search" = Option, Query, description = "Search keyword"), - ("sort_by" = Option, Query, description = "Sort by field"), - ("order" = Option, Query, description = "Order ASC or DESC"), - ("filter" = Option, Query, description = "Filter value"), - ("filter_by" = Option, Query, description = "Field to filter by"), - ), - responses( - (status = 200, description = "Get team list", body = ResponseListSuccessDto>), - (status = 200, description = "Get public team list", body = ResponseListSuccessDto>) - ), - tag = "Teams" + get, + security( + ("Bearer" = []) + ), + path = "/v1/teams", + params( + ("page" = Option, Query, description = "Page number"), + ("per_page" = Option, Query, description = "Items per page"), + ("search" = Option, Query, description = "Search keyword"), + ("sort_by" = Option, Query, description = "Sort by field"), + ("order" = Option, Query, description = "Order ASC or DESC"), + ("filter" = Option, Query, description = "Filter value"), + ("filter_by" = Option, Query, description = "Field to filter by"), + ), + responses( + (status = 200, description = "Get team list", body = ResponseListSuccessDto>), + (status = 200, description = "Get public team list", body = ResponseListSuccessDto>) + ), + tag = "Teams" )] pub async fn get_team_list( - headers: Option, - Extension(state): Extension, - axum::extract::Query(meta): axum::extract::Query, -) -> Response { - let state = state; - match headers { - Some(headers) => { - match permissions_guard(headers, axum::Extension(state.clone()), vec![]).await { - Ok((_claims, state)) => TeamsService::get_team_list(&state, meta).await, - Err(_) => TeamsService::get_public_team_list(&state, meta).await, - } - }, - None => TeamsService::get_public_team_list(&state, meta).await, - } + Extension(state): Extension, + axum::extract::Query(meta): axum::extract::Query, +) -> impl IntoResponse { + TeamsService::get_public_team_list(&state, meta).await } #[utoipa::path( - get, - path = "/v1/teams/{id}", - params( - ("id" = String, Path, description = "Team ID") - ), - responses( - (status = 200, description = "Get team by ID", body = ResponseSuccessDto), - (status = 200, description = "Get public team by ID", body = ResponseSuccessDto) - ), - tag = "Teams" + get, + path = "/v1/teams/{id}", + params( + ("id" = String, Path, description = "Team ID") + ), + responses( + (status = 200, description = "Get team by ID", body = ResponseSuccessDto), + (status = 200, description = "Get public team by ID", body = ResponseSuccessDto) + ), + tag = "Teams" )] pub async fn get_team_by_id( - headers: Option, - Extension(state): Extension, - Path(id): Path, -) -> Response { - let state = state; - match headers { - Some(headers) => { - match permissions_guard(headers, axum::Extension(state.clone()), vec![]).await { - Ok((_claims, state)) => TeamsService::get_team_by_id(&state, id).await, - Err(_) => TeamsService::get_public_team_by_id(&state, id).await, - } - }, - None => TeamsService::get_public_team_by_id(&state, id).await, - } + Extension(state): Extension, + Path(id): Path, +) -> impl IntoResponse { + TeamsService::get_public_team_by_id(&state, id).await } #[utoipa::path( @@ -313,87 +293,87 @@ pub async fn post_leave_current_team( } #[utoipa::path( - get, - security( - ("Bearer" = []) - ), - path = "/v1/teams/admin", - params( - ("page" = Option, Query, description = "Page number"), - ("per_page" = Option, Query, description = "Items per page"), - ("search" = Option, Query, description = "Search keyword"), - ("sort_by" = Option, Query, description = "Sort by field"), - ("order" = Option, Query, description = "Order ASC or DESC"), - ("filter" = Option, Query, description = "Filter value"), - ("filter_by" = Option, Query, description = "Field to filter by"), - ), - responses( - (status = 200, description = "Get admin team list", body = ResponseListSuccessDto>) - ), - tag = "Teams - Admin" + get, + security( + ("Bearer" = []) + ), + path = "/v1/teams/admin", + params( + ("page" = Option, Query, description = "Page number"), + ("per_page" = Option, Query, description = "Items per page"), + ("search" = Option, Query, description = "Search keyword"), + ("sort_by" = Option, Query, description = "Sort by field"), + ("order" = Option, Query, description = "Order ASC or DESC"), + ("filter" = Option, Query, description = "Filter value"), + ("filter_by" = Option, Query, description = "Field to filter by"), + ), + responses( + (status = 200, description = "Get admin team list", body = ResponseListSuccessDto>) + ), + tag = "Teams - Admin" )] pub async fn get_admin_team_list( - headers: HeaderMap, - Extension(state): Extension, - axum::extract::Query(meta): axum::extract::Query, + headers: HeaderMap, + Extension(state): Extension, + axum::extract::Query(meta): axum::extract::Query, ) -> Response { - let state = state; - with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadListTeams], move |_claims, state| { - let response = TeamsService::get_admin_team_list(&state, meta); - response - }).await + let state = state; + with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadListTeams], move |_claims, state| { + let response = TeamsService::get_admin_team_list(&state, meta); + response + }).await } #[utoipa::path( - get, - security( - ("Bearer" = []) - ), - path = "/v1/teams/admin/{id}", - params( - ("id" = String, Path, description = "Team ID") - ), - responses( - (status = 200, description = "Get admin team by ID", body = ResponseSuccessDto) - ), - tag = "Teams - Admin" + get, + security( + ("Bearer" = []) + ), + path = "/v1/teams/admin/{id}", + params( + ("id" = String, Path, description = "Team ID") + ), + responses( + (status = 200, description = "Get admin team by ID", body = ResponseSuccessDto) + ), + tag = "Teams - Admin" )] pub async fn get_admin_team_by_id( - headers: HeaderMap, - Extension(state): Extension, - Path(id): Path, + headers: HeaderMap, + Extension(state): Extension, + Path(id): Path, ) -> Response { - let state = state; - with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| { - let response = TeamsService::get_admin_team_by_id(&state, id); - response - }).await + let state = state; + with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| { + let response = TeamsService::get_admin_team_by_id(&state, id); + response + }).await } #[utoipa::path( - get, - security( - ("Bearer" = []) - ), - path = "/v1/teams/admin/{id}/members", - params( - ("id" = String, Path, description = "Team ID") - ), - responses( - (status = 200, description = "Get admin team members", body = ResponseSuccessDto>) - ), - tag = "Teams - Admin" + get, + security( + ("Bearer" = []) + ), + path = "/v1/teams/admin/{id}/members", + params( + ("id" = String, Path, description = "Team ID") + ), + responses( + (status = 200, description = "Get admin team members", body = ResponseSuccessDto>) + ), + tag = "Teams - Admin" )] pub async fn get_admin_team_members( - headers: HeaderMap, - Extension(state): Extension, - Path(id): Path, + headers: HeaderMap, + Extension(state): Extension, + Path(id): Path, ) -> Response { - let state = state; - with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| { - let response = TeamsService::get_admin_team_members(&state, id); - response - }).await + let state = state; + with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| { + let response = TeamsService::get_admin_team_members(&state, id); + response + }).await } pub fn teams_router() -> Router { diff --git a/imphnen-iam/src/v1/teams/teams_repository.rs b/imphnen-iam/src/v1/teams/teams_repository.rs index 7953cec..393d241 100644 --- a/imphnen-iam/src/v1/teams/teams_repository.rs +++ b/imphnen-iam/src/v1/teams/teams_repository.rs @@ -241,32 +241,41 @@ impl<'a> TeamsRepository<'a> { } pub async fn query_is_team_member(&self, team_id: &Thing, user_id: &Thing) -> Result { - let now = Instant::now(); - let db = &self.state.surrealdb_ws; - - let conditions = format!( - "{} AND is_active = true", - build_multi_thing_condition(&[("team_id", team_id), ("user_id", user_id)]) - ); - - let member_count = execute_safe_count_query( - db, - ResourceEnum::TeamMembers.to_string(), - &conditions, - ).await.unwrap_or(0); - - let elapsed = now.elapsed(); - - if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()) - == "development" - { - println!("Query 'query_is_team_member' found {} matching members", member_count); - println!("Query 'query_is_team_member' took: {elapsed:.2?}"); + let now = Instant::now(); + let db = &self.state.surrealdb_ws; + + // Use direct SQL query for more control over the team member check + let sql = format!( + "SELECT COUNT() AS count FROM {} + WHERE team_id = $team_id + AND user_id = $user_id + AND is_active = true", + ResourceEnum::TeamMembers + ); + + let mut result = db.query(sql) + .bind(("team_id", team_id.id.to_raw())) + .bind(("user_id", user_id.id.to_raw())) + .await?; + + // Use a simpler approach to get the count + let count = match result.take(0) { + Ok(Some(surrealdb::sql::Value::Number(num))) => num.to_int(), + _ => 0, + }; + + let elapsed = now.elapsed(); + + if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()) + == "development" + { + println!("Query 'query_is_team_member' found {} matching members", count); + println!("Query 'query_is_team_member' took: {elapsed:.2?}"); + } + + Ok(count > 0) } - Ok(member_count > 0) - } - pub async fn query_create_invitation(&self, data: TeamInvitationsSchema) -> Result { let now = Instant::now(); let db = &self.state.surrealdb_ws; diff --git a/tests/src/iam/teams/teams_service_test.rs b/tests/src/iam/teams/teams_service_test.rs index 22a1bc0..cf959cc 100644 --- a/tests/src/iam/teams/teams_service_test.rs +++ b/tests/src/iam/teams/teams_service_test.rs @@ -476,8 +476,9 @@ mod tests { ); repo.query_add_team_member(member_member).await.unwrap(); - // Verify non-leader is a member - let is_member = repo.query_is_team_member(&team_thing, &make_thing_from_enum(ResourceEnum::Users, &non_leader.id.id.to_raw())).await.unwrap(); + // Verify non-leader is a member by directly checking team members + let members = repo.query_team_members(&team_thing).await.unwrap(); + let is_member = members.iter().any(|m| m.user_id.id.to_raw() == non_leader.id.id.to_raw()); assert!(is_member, "Non-leader should be a team member"); // Try to remove member as non-leader (this would fail in real service layer with auth)