use crate::handlers::AppState; use crate::middleware::TenantId; use crate::middleware::auth::AuthContext; use crate::models::{CreateRoleRequest, RoleResponse}; use axum::{Json, extract::State}; use common_telemetry::{AppError, AppResponse}; use tracing::instrument; #[utoipa::path( post, path = "/roles", tag = "Role", security( ("bearer_auth" = []) ), request_body = CreateRoleRequest, responses( (status = 201, description = "角色创建成功", body = RoleResponse), (status = 400, description = "请求参数错误"), (status = 401, description = "未认证"), (status = 403, description = "无权限") ), params( ("Authorization" = String, Header, description = "Bearer (访问令牌)"), ("X-Tenant-ID" = String, Header, description = "租户 UUID(可选,若提供需与 Token 中 tenant_id 一致)") ) )] #[instrument(skip(state, payload))] /// 在当前租户下创建角色。 /// /// 业务规则: /// - 角色归属到当前租户(由 `TenantId` 决定),禁止跨租户写入。 /// - 需要具备 `role:write` 权限,否则返回 403。 /// /// 输入: /// - Header `Authorization: Bearer `(必填) /// - Body `CreateRoleRequest`(必填) /// /// 输出: /// - `201`:返回新建角色信息(含 `id`) /// /// 异常: /// - `401`:未认证 /// - `403`:租户不匹配或无权限 /// - `400`:请求参数错误 pub async fn create_role_handler( TenantId(tenant_id): TenantId, State(state): State, AuthContext { tenant_id: auth_tenant_id, user_id, .. }: AuthContext, Json(payload): Json, ) -> Result, AppError> { if auth_tenant_id != tenant_id { return Err(AppError::PermissionDenied("tenant:mismatch".into())); } state .authorization_service .require_permission(tenant_id, user_id, "role:write") .await?; let role = state.role_service.create_role(tenant_id, payload).await?; Ok(AppResponse::created(RoleResponse { id: role.id, name: role.name, description: role.description, })) } #[utoipa::path( get, path = "/roles", tag = "Role", security( ("bearer_auth" = []) ), responses( (status = 200, description = "角色列表", body = [RoleResponse]), (status = 401, description = "未认证"), (status = 403, description = "无权限") ), params( ("Authorization" = String, Header, description = "Bearer (访问令牌)"), ("X-Tenant-ID" = String, Header, description = "租户 UUID(可选,若提供需与 Token 中 tenant_id 一致)") ) )] #[instrument(skip(state))] /// 查询当前租户下的角色列表。 /// /// 业务规则: /// - 仅返回当前租户角色;若 `X-Tenant-ID` 与 Token 不一致则返回 403。 /// - 需要具备 `role:read` 权限。 /// /// 输入: /// - Header `Authorization: Bearer `(必填) /// /// 输出: /// - `200`:角色列表 /// /// 异常: /// - `401`:未认证 /// - `403`:租户不匹配或无权限 pub async fn list_roles_handler( TenantId(tenant_id): TenantId, State(state): State, AuthContext { tenant_id: auth_tenant_id, user_id, .. }: AuthContext, ) -> Result>, AppError> { if auth_tenant_id != tenant_id { return Err(AppError::PermissionDenied("tenant:mismatch".into())); } state .authorization_service .require_permission(tenant_id, user_id, "role:read") .await?; let roles = state.role_service.list_roles(tenant_id).await?; let response = roles .into_iter() .map(|r| RoleResponse { id: r.id, name: r.name, description: r.description, }) .collect(); Ok(AppResponse::ok(response)) }