diff --git a/src/handlers/permission.rs b/src/handlers/permission.rs index 277039f..52704ae 100644 --- a/src/handlers/permission.rs +++ b/src/handlers/permission.rs @@ -26,6 +26,41 @@ use tracing::instrument; ) )] #[instrument(skip(state))] +/// 查询权限列表(Permission Catalog)。 +/// +/// 返回全局权限目录(`permissions` 表)的分页结果,用于: +/// - 后台展示可分配权限; +/// - 角色绑定权限前的检索与校验; +/// - 按应用(`app_code`)/资源(`resource`)/动作(`action`)筛选。 +/// +/// 权限编码规范为 `${app_code}:${resource}:${action}`,例如 `cms:article:publish`。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID(用于鉴权与跨租户隔离)。 +/// - `State(state): State`:应用状态,包含 `PermissionService`/`AuthorizationService`。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。 +/// - `Query(query): Query`:查询参数(分页/搜索/筛选/排序)。 +/// - `page: Option`:页码(默认 1)。 +/// - `page_size: Option`:每页条数(默认 20,范围 1..=200)。 +/// - `search: Option`:按 `code` 或 `description` 模糊搜索。 +/// - `app_code/resource/action`:精确筛选(用于权限目录分组)。 +/// +/// ## Returns +/// - `Ok(AppResponse>)`:成功返回 `200`,`data` 为权限列表。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("role:read"))`:调用方缺少 `role:read` 权限(用于控制“查看权限目录”的能力)。 +/// - `Err(AppError::BadRequest(_))`:分页参数非法(如 `page_size>200` 或为 0)。 +/// - `Err(AppError::DbError(_))`:数据库查询失败。 +/// +/// ## Example +/// ```rust,ignore +/// // GET /permissions?page=1&page_size=20&app_code=cms&search=article +/// // Headers: +/// // Authorization: Bearer +/// // X-Tenant-ID: // 可选,但若提供必须与 token 中 tenant_id 一致 +/// ``` pub async fn list_permissions_handler( TenantId(tenant_id): TenantId, State(state): State, diff --git a/src/handlers/role.rs b/src/handlers/role.rs index cbbeeea..57010a8 100644 --- a/src/handlers/role.rs +++ b/src/handlers/role.rs @@ -32,24 +32,36 @@ use uuid::Uuid; ) )] #[instrument(skip(state, payload))] -/// Create role in current tenant. -/// 在当前租户下创建角色。 +/// 在当前租户下创建自定义角色(Role)。 /// -/// 业务规则: -/// - 角色归属到当前租户(由 `TenantId` 决定),禁止跨租户写入。 -/// - 需要具备 `role:write` 权限,否则返回 403。 +/// 本接口用于创建 **租户级自定义角色**(`roles.tenant_id = 当前租户`)。创建后可继续通过 +/// “角色-权限绑定”接口为角色授予权限,再通过“用户-角色绑定”接口把角色授予用户。 /// -/// 输入: -/// - Header `Authorization: Bearer `(必填) -/// - Body `CreateRoleRequest`(必填) +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(内部包含 `Uuid`):当前请求的租户 ID(由中间件解析并注入)。 +/// - `State(state): State`:应用状态,包含 `RoleService`/`AuthorizationService` 等依赖。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`: +/// 从 `Authorization: Bearer ` 解析得到的认证上下文,包含调用方用户 ID 与租户 ID。 +/// - `Json(payload): Json`:创建角色的请求体(角色名与描述)。 /// -/// 输出: -/// - `201`:返回新建角色信息(含 `id`) +/// ## Returns +/// - `Ok(AppResponse)`:创建成功返回 `201`,`data` 为新建角色(含 `id/name/description`)。 /// -/// 异常: -/// - `401`:未认证 -/// - `403`:租户不匹配或无权限 -/// - `400`:请求参数错误 +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:Token 中租户与当前租户不一致(跨租户访问被拒绝)。 +/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。 +/// - `Err(AppError::AlreadyExists(_))`:角色名冲突(同租户下同名角色已存在)。 +/// - `Err(AppError::DbError(_))`:数据库写入失败(连接异常、约束失败等)。 +/// +/// ## Example +/// ```rust,ignore +/// // POST /roles +/// // Headers: +/// // Authorization: Bearer +/// // X-Tenant-ID: // 可选,但若提供必须与 token 中 tenant_id 一致 +/// // Body: +/// // { "name": "ContentAdmin", "description": "CMS content admins" } +/// ``` pub async fn create_role_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -97,22 +109,23 @@ pub async fn create_role_handler( ) )] #[instrument(skip(state))] -/// List roles in current tenant. -/// 查询当前租户下的角色列表。 +/// 查询当前租户下的角色列表(Role)。 /// -/// 业务规则: -/// - 仅返回当前租户角色;若 `X-Tenant-ID` 与 Token 不一致则返回 403。 -/// - 需要具备 `role:read` 权限。 +/// 返回当前租户的所有角色(包含系统角色与自定义角色)。系统角色通常 `is_system=true`, +/// 用于内置管理员/平台管理员等能力;自定义角色用于业务场景的权限组合。 /// -/// 输入: -/// - Header `Authorization: Bearer `(必填) +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前请求的租户 ID。 +/// - `State(state): State>)`:成功返回 `200`,`data` 为角色数组。 /// -/// 异常: -/// - `401`:未认证 -/// - `403`:租户不匹配或无权限 +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("role:read"))`:调用方缺少 `role:read` 权限。 +/// - `Err(AppError::DbError(_))`:数据库查询失败。 pub async fn list_roles_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -162,6 +175,24 @@ pub async fn list_roles_handler( ) )] #[instrument(skip(state))] +/// 查询角色详情(Role)。 +/// +/// 仅允许读取当前租户内的角色,避免跨租户信息泄露。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID。 +/// - `State(state): State`:应用状态。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。 +/// - `Path(role_id): Path`:目标角色 ID。 +/// +/// ## Returns +/// - `Ok(AppResponse)`:成功返回 `200`,`data` 为角色信息。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("role:read"))`:调用方缺少 `role:read` 权限。 +/// - `Err(AppError::NotFound(_))`:角色不存在或不属于当前租户。 +/// - `Err(AppError::DbError(_))`:数据库查询失败。 pub async fn get_role_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -209,6 +240,28 @@ pub async fn get_role_handler( ) )] #[instrument(skip(state, payload))] +/// 更新角色基础信息(Role)。 +/// +/// 仅允许修改 **自定义角色**。当目标角色为系统角色(`is_system=true`)时返回 403, +/// 以防止线上误操作导致全局权限体系漂移。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID。 +/// - `State(state): State`:应用状态。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。 +/// - `Path(role_id): Path`:目标角色 ID。 +/// - `Json(payload): Json`:可选更新字段(`name`/`description`)。 +/// +/// ## Returns +/// - `Ok(AppResponse)`:成功返回 `200`,`data` 为更新后的角色信息。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。 +/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可修改。 +/// - `Err(AppError::AlreadyExists(_))`:角色名冲突。 +/// - `Err(AppError::NotFound(_))`:角色不存在。 +/// - `Err(AppError::DbError(_))`:数据库写入失败。 pub async fn update_role_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -258,6 +311,26 @@ pub async fn update_role_handler( ) )] #[instrument(skip(state))] +/// 删除角色(Role)。 +/// +/// 仅允许删除 **自定义角色**;系统角色(`is_system=true`)不可删除。 +/// 删除角色会级联删除 `user_roles` 与 `role_permissions` 关联(由外键约束实现)。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID。 +/// - `State(state): State`:应用状态。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。 +/// - `Path(role_id): Path`:目标角色 ID。 +/// +/// ## Returns +/// - `Ok(AppResponse)`:成功返回 `200`,`data` 为 `{}`。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。 +/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可删除。 +/// - `Err(AppError::NotFound(_))`:角色不存在。 +/// - `Err(AppError::DbError(_))`:数据库删除失败。 pub async fn delete_role_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -304,6 +377,38 @@ pub async fn delete_role_handler( ) )] #[instrument(skip(state, payload))] +/// 为角色批量授予权限(Role → Permission 绑定)。 +/// +/// 将 `permission_codes` 批量写入 `role_permissions`(幂等:重复绑定不会报错)。 +/// 仅允许对 **自定义角色** 执行此操作;系统角色不可通过 API 修改权限集。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID。 +/// - `State(state): State`:应用状态。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。 +/// - `Path(role_id): Path`:目标角色 ID。 +/// - `Json(payload): Json`:待授予的权限码数组(`Vec`)。 +/// +/// ## Returns +/// - `Ok(AppResponse)`:成功返回 `200`,`data` 为 `{}`。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。 +/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可变更权限集。 +/// - `Err(AppError::BadRequest(_))`:存在非法的 `permission_codes`(权限码未在 `permissions` 表中定义)。 +/// - `Err(AppError::NotFound(_))`:角色不存在。 +/// - `Err(AppError::DbError(_))`:数据库写入失败。 +/// +/// ## Example +/// ```rust,ignore +/// // POST /roles/{role_id}/permissions/grant +/// // Body: +/// // { "permission_codes": ["cms:article:create", "cms:article:publish", "cms:*:*"] } +/// // +/// // 说明: +/// // - 通配符权限(如 cms:*:*)只有在 permissions 表中存在并被绑定到角色时才会生效。 +/// ``` pub async fn grant_role_permissions_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -351,6 +456,27 @@ pub async fn grant_role_permissions_handler( ) )] #[instrument(skip(state, payload))] +/// 从角色批量回收权限(Role → Permission 解绑)。 +/// +/// 从 `role_permissions` 删除指定 `permission_codes` 的关联(幂等:不存在的关联不会报错)。 +/// 仅允许对 **自定义角色** 执行此操作;系统角色不可通过 API 修改权限集。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID。 +/// - `State(state): State`:应用状态。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。 +/// - `Path(role_id): Path`:目标角色 ID。 +/// - `Json(payload): Json`:待回收的权限码数组(`Vec`)。 +/// +/// ## Returns +/// - `Ok(AppResponse)`:成功返回 `200`,`data` 为 `{}`。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。 +/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可变更权限集。 +/// - `Err(AppError::NotFound(_))`:角色不存在。 +/// - `Err(AppError::DbError(_))`:数据库写入失败。 pub async fn revoke_role_permissions_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -398,6 +524,34 @@ pub async fn revoke_role_permissions_handler( ) )] #[instrument(skip(state, payload))] +/// 批量把角色授予用户(User ← Role 绑定)。 +/// +/// 将 `user_ids` 批量写入 `user_roles`(幂等:重复授予不会报错)。 +/// 该接口用于“按角色”为多个用户授权,适用于组织/部门批量开通权限的场景。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID。 +/// - `State(state): State`:应用状态。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文(`user_id` 为操作者)。 +/// - `Path(role_id): Path`:目标角色 ID。 +/// - `Json(payload): Json`:待授予用户 ID 列表(`Vec`)。 +/// +/// ## Returns +/// - `Ok(AppResponse)`:成功返回 `200`,`data` 为 `{}`。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("user:write"))`:调用方缺少 `user:write` 权限。 +/// - `Err(AppError::NotFound(_))`:角色不存在。 +/// - `Err(AppError::BadRequest(_))`:存在非法 `user_ids`(用户不在当前租户内)。 +/// - `Err(AppError::DbError(_))`:数据库写入失败。 +/// +/// ## Example +/// ```rust,ignore +/// // POST /roles/{role_id}/users/grant +/// // Body: +/// // { "user_ids": ["", ""] } +/// ``` pub async fn grant_role_users_handler( TenantId(tenant_id): TenantId, State(state): State, @@ -445,6 +599,25 @@ pub async fn grant_role_users_handler( ) )] #[instrument(skip(state, payload))] +/// 批量从用户回收角色(User ← Role 解绑)。 +/// +/// 从 `user_roles` 删除指定 `user_ids` 的角色关联(幂等:不存在的关联不会报错)。 +/// +/// ## Parameters +/// - `TenantId(tenant_id): TenantId`(`Uuid`):当前租户 ID。 +/// - `State(state): State`:应用状态。 +/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文(`user_id` 为操作者)。 +/// - `Path(role_id): Path`:目标角色 ID。 +/// - `Json(payload): Json`:待回收用户 ID 列表(`Vec`)。 +/// +/// ## Returns +/// - `Ok(AppResponse)`:成功返回 `200`,`data` 为 `{}`。 +/// +/// ## Errors +/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。 +/// - `Err(AppError::PermissionDenied("user:write"))`:调用方缺少 `user:write` 权限。 +/// - `Err(AppError::NotFound(_))`:角色不存在。 +/// - `Err(AppError::DbError(_))`:数据库写入失败。 pub async fn revoke_role_users_handler( TenantId(tenant_id): TenantId, State(state): State,