From dd4a73e3608746fcd76e5a713be83177e2c71c7d Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Thu, 25 Sep 2025 20:28:08 +0700 Subject: [PATCH] feat: Add comprehensive admin team management functionality with new permissions and endpoints --- .../src/bin/seed_roles_permissions.rs | 7 + .../src/v1/permissions/permissions_enum.rs | 24 ++ .../src/v1/teams/admin_teams_controller.rs | 217 ++++++++++++++++++ imphnen-iam/src/v1/teams/mod.rs | 8 + imphnen-iam/src/v1/teams/teams_controller.rs | 19 ++ 5 files changed, 275 insertions(+) create mode 100644 imphnen-iam/src/v1/teams/admin_teams_controller.rs diff --git a/imphnen-backend/src/bin/seed_roles_permissions.rs b/imphnen-backend/src/bin/seed_roles_permissions.rs index dea3f7a..eecd956 100644 --- a/imphnen-backend/src/bin/seed_roles_permissions.rs +++ b/imphnen-backend/src/bin/seed_roles_permissions.rs @@ -34,6 +34,13 @@ async fn main() -> Result<(), Box> { PermissionsEnum::DeleteUsers, PermissionsEnum::UpdateUsers, PermissionsEnum::ActivateUsers, + // Full admin permissions + PermissionsEnum::ManageAllUsers, + PermissionsEnum::ManageAllRoles, + PermissionsEnum::ManageAllPermissions, + PermissionsEnum::ManageAllTeams, + PermissionsEnum::ViewAllSensitiveData, + PermissionsEnum::AccessAdminDashboard, PermissionsEnum::ReadListRoles, PermissionsEnum::ReadDetailRoles, PermissionsEnum::CreateRoles, diff --git a/imphnen-iam/src/v1/permissions/permissions_enum.rs b/imphnen-iam/src/v1/permissions/permissions_enum.rs index 2ccbeb9..0315cdb 100644 --- a/imphnen-iam/src/v1/permissions/permissions_enum.rs +++ b/imphnen-iam/src/v1/permissions/permissions_enum.rs @@ -107,6 +107,14 @@ impl fmt::Display for PermissionsEnum { PermissionsEnum::UpdateMentors => "Update Mentors", PermissionsEnum::VerifyMentors => "Verify Mentors", PermissionsEnum::DeleteMentors => "Delete Mentors", + + // Administrator permissions + PermissionsEnum::ManageAllUsers => "Manage All Users", + PermissionsEnum::ManageAllRoles => "Manage All Roles", + PermissionsEnum::ManageAllPermissions => "Manage All Permissions", + PermissionsEnum::ManageAllTeams => "Manage All Teams", + PermissionsEnum::ViewAllSensitiveData => "View All Sensitive Data", + PermissionsEnum::AccessAdminDashboard => "Access Admin Dashboard", }; write!(f, "{permission_str}") } @@ -164,6 +172,14 @@ impl PermissionsEnum { PermissionsEnum::UpdateMentors => "a7b8c9d0-1234-5678-0123-789012345678".to_string(), PermissionsEnum::VerifyMentors => "b8c9d0e1-2345-6789-1234-890123456789".to_string(), PermissionsEnum::DeleteMentors => "c9d0e1f2-3456-7890-2345-901234567890".to_string(), + + // Administrator permissions + PermissionsEnum::ManageAllUsers => "d0e1f2a3-4567-8901-2345-0123456789ab".to_string(), + PermissionsEnum::ManageAllRoles => "e1f2a3b4-5678-9012-3456-1234567890ab".to_string(), + PermissionsEnum::ManageAllPermissions => "f2a3b4c5-6789-0123-4567-2345678901ab".to_string(), + PermissionsEnum::ManageAllTeams => "a3b4c5d6-7890-1234-5678-3456789012ab".to_string(), + PermissionsEnum::ViewAllSensitiveData => "b4c5d6e7-8901-2345-6789-4567890123ab".to_string(), + PermissionsEnum::AccessAdminDashboard => "c5d6e7f8-9012-3456-7890-5678901234ab".to_string(), } } @@ -224,6 +240,14 @@ impl PermissionsEnum { PermissionsEnum::UpdateMentors, PermissionsEnum::VerifyMentors, PermissionsEnum::DeleteMentors, + + // Administrator permissions + PermissionsEnum::ManageAllUsers, + PermissionsEnum::ManageAllRoles, + PermissionsEnum::ManageAllPermissions, + PermissionsEnum::ManageAllTeams, + PermissionsEnum::ViewAllSensitiveData, + PermissionsEnum::AccessAdminDashboard, ] } } diff --git a/imphnen-iam/src/v1/teams/admin_teams_controller.rs b/imphnen-iam/src/v1/teams/admin_teams_controller.rs new file mode 100644 index 0000000..9e4d9db --- /dev/null +++ b/imphnen-iam/src/v1/teams/admin_teams_controller.rs @@ -0,0 +1,217 @@ +use crate::{AppState, MetaRequestDto}; +use crate::{ + MessageResponseDto, ResponseListSuccessDto, ResponseSuccessDto, + TeamsCreateRequestDto, TeamsUpdateRequestDto, TeamInviteRequestDto, + TeamAcceptInvitationRequestDto, TeamMemberDto, TeamsSearchQueryDto, + AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum +}; +use axum::response::Response; +use axum::extract::Path; +use axum::http::HeaderMap; +use axum::response::IntoResponse; +use axum::{Extension, Json}; +use super::teams_service::{TeamsServiceTrait, TeamsService}; +use axum::Router; + +/// Helper function for admin endpoints requiring specific permissions +async fn with_admin_perms( + headers: HeaderMap, + state: Extension, + f: F, +) -> Response +where + F: FnOnce(crate::Claims, AppState) -> Fut, + Fut: std::future::Future + Send, +{ + match crate::permissions_guard(headers, state, vec![PermissionsEnum::ManageAllTeams]).await { + Ok((claims, state)) => f(claims, state).await, + Err(response) => response, + } +} + +#[utoipa::path( + get, + security( + ("Bearer" = []) + ), + path = "/v1/admin/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 all teams (admin)", body = ResponseListSuccessDto>) + ), + tag = "Admin - Teams" +)] +pub async fn get_all_teams( + headers: HeaderMap, + Extension(state): Extension, + axum::extract::Query(meta): axum::extract::Query, +) -> Response { + with_admin_perms(headers, Extension(state), move |_claims, state| { + TeamsService::get_admin_team_list(&state, meta) + }).await +} + +#[utoipa::path( + get, + security( + ("Bearer" = []) + ), + path = "/v1/admin/teams/{id}", + params( + ("id" = String, Path, description = "Team ID") + ), + responses( + (status = 200, description = "Get team by ID (admin)", body = ResponseSuccessDto) + ), + tag = "Admin - Teams" +)] +pub async fn get_team_by_id( + headers: HeaderMap, + Extension(state): Extension, + Path(id): Path, +) -> Response { + with_admin_perms(headers, Extension(state), move |_claims, state| { + TeamsService::get_admin_team_by_id(&state, id) + }).await +} + +#[utoipa::path( + get, + security( + ("Bearer" = []) + ), + path = "/v1/admin/teams/{id}/members", + params( + ("id" = String, Path, description = "Team ID") + ), + responses( + (status = 200, description = "Get team members (admin)", body = ResponseSuccessDto>) + ), + tag = "Admin - Teams" +)] +pub async fn get_team_members( + headers: HeaderMap, + Extension(state): Extension, + Path(id): Path, +) -> Response { + with_admin_perms(headers, Extension(state), move |_claims, state| { + TeamsService::get_admin_team_members(&state, id) + }).await +} + +#[utoipa::path( + post, + security( + ("Bearer" = []) + ), + path = "/v1/admin/teams", + request_body = TeamsCreateRequestDto, + responses( + (status = 200, description = "Create team (admin)", body = ResponseSuccessDto) + ), + tag = "Admin - Teams" +)] +pub async fn create_team( + headers: HeaderMap, + Extension(state): Extension, + Json(payload): Json, +) -> impl IntoResponse { + with_admin_perms(headers, Extension(state), move |claims, state| { + TeamsService::create_team(&state, claims, payload) + }).await +} + +#[utoipa::path( + put, + security( + ("Bearer" = []) + ), + path = "/v1/admin/teams/{id}", + params( + ("id" = String, Path, description = "Team ID") + ), + request_body = TeamsUpdateRequestDto, + responses( + (status = 200, description = "Update team (admin)", body = MessageResponseDto) + ), + tag = "Admin - Teams" +)] +pub async fn update_team( + headers: HeaderMap, + Extension(state): Extension, + Path(id): Path, + Json(payload): Json, +) -> impl IntoResponse { + with_admin_perms(headers, Extension(state), move |claims, state| { + TeamsService::update_team(&state, claims, id, payload) + }).await +} + +#[utoipa::path( + delete, + security( + ("Bearer" = []) + ), + path = "/v1/admin/teams/{id}", + params( + ("id" = String, Path, description = "Team ID") + ), + responses( + (status = 200, description = "Delete team (admin)", body = MessageResponseDto) + ), + tag = "Admin - Teams" +)] +pub async fn delete_team( + headers: HeaderMap, + Extension(state): Extension, + Path(id): Path, +) -> impl IntoResponse { + with_admin_perms(headers, Extension(state), move |claims, state| { + TeamsService::delete_team(&state, claims, id) + }).await +} + +#[utoipa::path( + post, + security( + ("Bearer" = []) + ), + path = "/v1/admin/teams/{id}/invite", + params( + ("id" = String, Path, description = "Team ID") + ), + request_body = TeamInviteRequestDto, + responses( + (status = 200, description = "Invite team members (admin)", body = ResponseSuccessDto) + ), + tag = "Admin - Teams" +)] +pub async fn invite_team_members( + headers: HeaderMap, + Extension(state): Extension, + Path(team_id): Path, + Json(payload): Json, +) -> impl IntoResponse { + with_admin_perms(headers, Extension(state), move |claims, state| { + TeamsService::invite_team_members(&state, claims, team_id, payload) + }).await +} + +pub fn admin_teams_router() -> Router { + Router::new() + .route("/", axum::routing::get(get_all_teams)) + .route("/:id", axum::routing::get(get_team_by_id)) + .route("/:id/members", axum::routing::get(get_team_members)) + .route("/", axum::routing::post(create_team)) + .route("/:id", axum::routing::put(update_team)) + .route("/:id", axum::routing::delete(delete_team)) + .route("/:id/invite", axum::routing::post(invite_team_members)) +} \ No newline at end of file diff --git a/imphnen-iam/src/v1/teams/mod.rs b/imphnen-iam/src/v1/teams/mod.rs index d01a872..4dd2dda 100644 --- a/imphnen-iam/src/v1/teams/mod.rs +++ b/imphnen-iam/src/v1/teams/mod.rs @@ -1,9 +1,11 @@ +pub mod admin_teams_controller; pub mod teams_controller; pub mod teams_dto; pub mod teams_repository; pub mod teams_schema; pub mod teams_service; +pub use admin_teams_controller::*; pub use teams_controller::*; pub use teams_dto::*; pub use teams_repository::*; @@ -11,7 +13,13 @@ 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() + // Public routes + .merge(teams_controller::teams_router()) + // Admin routes + .merge(admin_teams_controller::admin_teams_router()) } \ No newline at end of file diff --git a/imphnen-iam/src/v1/teams/teams_controller.rs b/imphnen-iam/src/v1/teams/teams_controller.rs index 3d9eb0c..df82297 100644 --- a/imphnen-iam/src/v1/teams/teams_controller.rs +++ b/imphnen-iam/src/v1/teams/teams_controller.rs @@ -12,6 +12,7 @@ use axum::http::HeaderMap; use axum::response::IntoResponse; use axum::{Extension, Json}; use super::teams_service::{TeamsServiceTrait, TeamsService}; +use axum::Router; // Helper function for endpoints requiring authentication without specific permissions async fn authenticated( @@ -394,3 +395,21 @@ pub async fn get_admin_team_members( response }).await } + +pub fn teams_router() -> Router { + Router::new() + .route("/", axum::routing::get(get_team_list)) + .route("/:id", axum::routing::get(get_team_by_id)) + .route("/create", axum::routing::post(post_create_team)) + .route("/update/:id", axum::routing::put(put_update_team)) + .route("/delete/:id", axum::routing::delete(delete_team)) + .route("/:id/invite", axum::routing::post(post_invite_team_members)) + .route("/accept/:token", axum::routing::post(post_accept_invitation)) + .route("/search", axum::routing::get(get_public_team_search)) + .route("/:id/members", axum::routing::get(get_team_members)) + .route("/:id/leave", axum::routing::post(post_leave_team)) + .route("/leave-me", axum::routing::post(post_leave_current_team)) + .route("/admin", axum::routing::get(get_admin_team_list)) + .route("/admin/:id", axum::routing::get(get_admin_team_by_id)) + .route("/admin/:id/members", axum::routing::get(get_admin_team_members)) +}