fix(doc): fix doc
This commit is contained in:
@@ -16,6 +16,18 @@
|
|||||||
- `apps`:租户已开通应用列表(如 `["cms","tms"]`)
|
- `apps`:租户已开通应用列表(如 `["cms","tms"]`)
|
||||||
- `apps_version`:租户 enabled_apps 版本号(用于客户端判断是否需要刷新会话)
|
- `apps_version`:租户 enabled_apps 版本号(用于客户端判断是否需要刷新会话)
|
||||||
|
|
||||||
|
## OpenAPI/Scalar 可配置参数(环境变量)
|
||||||
|
|
||||||
|
为方便本地调试与演示,服务会在生成 OpenAPI 文档时,将部分 Header 参数的示例值按环境变量动态注入(影响 `/scalar` 展示,不影响服务鉴权逻辑):
|
||||||
|
|
||||||
|
- `IAM_DOCS_DEFAULT_TENANT_ID`:默认租户 ID(用于 `X-Tenant-ID` Header 的 example)
|
||||||
|
- 未设置时默认:`11111111-1111-1111-1111-111111111111`
|
||||||
|
- 可选强制:`IAM_DOCS_REQUIRE_TENANT_ID=1`(未设置则启动时直接失败)
|
||||||
|
- `IAM_DOCS_DEFAULT_TOKEN`:默认 Token(用于 `Authorization` Header 的 example)
|
||||||
|
- 可填写裸 JWT 或 `Bearer <jwt>`;裸 JWT 会自动补齐 `Bearer `
|
||||||
|
- 未设置时默认:`Bearer <access_token>`
|
||||||
|
- 可选强制:`IAM_DOCS_REQUIRE_TOKEN=1`(未设置则启动时直接失败)
|
||||||
|
|
||||||
## 通用响应结构
|
## 通用响应结构
|
||||||
|
|
||||||
成功响应:
|
成功响应:
|
||||||
@@ -63,6 +75,7 @@
|
|||||||
|
|
||||||
**POST** `/tenants/register`
|
**POST** `/tenants/register`
|
||||||
|
|
||||||
|
- Tag:`Tenant`
|
||||||
- Header:无
|
- Header:无
|
||||||
- Body:
|
- Body:
|
||||||
|
|
||||||
@@ -88,6 +101,7 @@
|
|||||||
|
|
||||||
**POST** `/auth/register`
|
**POST** `/auth/register`
|
||||||
|
|
||||||
|
- Tag:`Auth`
|
||||||
- Header:`X-Tenant-ID: 00000000-0000-0000-0000-000000000001`
|
- Header:`X-Tenant-ID: 00000000-0000-0000-0000-000000000001`
|
||||||
- Body:
|
- Body:
|
||||||
|
|
||||||
@@ -99,6 +113,7 @@
|
|||||||
|
|
||||||
**POST** `/auth/register`
|
**POST** `/auth/register`
|
||||||
|
|
||||||
|
- Tag:`Auth`
|
||||||
- 必需 Header:`X-Tenant-ID: <tenant_id>`
|
- 必需 Header:`X-Tenant-ID: <tenant_id>`
|
||||||
- Body:
|
- Body:
|
||||||
|
|
||||||
@@ -116,6 +131,7 @@
|
|||||||
|
|
||||||
**POST** `/auth/login`
|
**POST** `/auth/login`
|
||||||
|
|
||||||
|
- Tag:`Auth`
|
||||||
- 必需 Header:`X-Tenant-ID: <tenant_id>`
|
- 必需 Header:`X-Tenant-ID: <tenant_id>`
|
||||||
- Body:
|
- Body:
|
||||||
|
|
||||||
@@ -135,6 +151,7 @@
|
|||||||
|
|
||||||
**GET** `/tenants/me`
|
**GET** `/tenants/me`
|
||||||
|
|
||||||
|
- Tag:`Tenant`
|
||||||
- 必需 Header:`Authorization: Bearer <access_token>`
|
- 必需 Header:`Authorization: Bearer <access_token>`
|
||||||
- 可选 Header:`X-Tenant-ID: <tenant_id>`(如提供必须与 token tenant_id 一致)
|
- 可选 Header:`X-Tenant-ID: <tenant_id>`(如提供必须与 token tenant_id 一致)
|
||||||
|
|
||||||
@@ -148,6 +165,7 @@
|
|||||||
|
|
||||||
**GET** `/me/permissions`
|
**GET** `/me/permissions`
|
||||||
|
|
||||||
|
- Tag:`Me`
|
||||||
- 必需 Header:`Authorization: Bearer <access_token>`
|
- 必需 Header:`Authorization: Bearer <access_token>`
|
||||||
|
|
||||||
成功(200):
|
成功(200):
|
||||||
@@ -169,12 +187,14 @@
|
|||||||
|
|
||||||
**GET** `/platform/tenants/{tenant_id}/enabled-apps`
|
**GET** `/platform/tenants/{tenant_id}/enabled-apps`
|
||||||
|
|
||||||
|
- Tag:`Tenant`
|
||||||
- Header:`Authorization: Bearer <access_token>`(平台租户下登录得到的 token)
|
- Header:`Authorization: Bearer <access_token>`(平台租户下登录得到的 token)
|
||||||
|
|
||||||
#### 4.1.2 设置某租户 enabled_apps(全量覆盖,幂等)
|
#### 4.1.2 设置某租户 enabled_apps(全量覆盖,幂等)
|
||||||
|
|
||||||
**PUT** `/platform/tenants/{tenant_id}/enabled-apps`
|
**PUT** `/platform/tenants/{tenant_id}/enabled-apps`
|
||||||
|
|
||||||
|
- Tag:`Tenant`
|
||||||
- Header:`Authorization: Bearer <access_token>`
|
- Header:`Authorization: Bearer <access_token>`
|
||||||
- Body:
|
- Body:
|
||||||
|
|
||||||
@@ -185,11 +205,18 @@
|
|||||||
说明:
|
说明:
|
||||||
- `expected_version` 可选,用于并发控制;不匹配会返回 409。
|
- `expected_version` 可选,用于并发控制;不匹配会返回 409。
|
||||||
- 登录签发 token 时会自动把 `apps/apps_version` 注入到 JWT,并对 `permissions` 按 enabled_apps 过滤。
|
- 登录签发 token 时会自动把 `apps/apps_version` 注入到 JWT,并对 `permissions` 按 enabled_apps 过滤。
|
||||||
|
- `enabled_apps` 必须是“应用注册表 apps 表”中存在且 `status=active` 的应用 ID;若传入未知/已下线应用(例如 `dms`),接口会返回 400。
|
||||||
|
|
||||||
|
enabled_apps 维护建议:
|
||||||
|
- 新增应用:通过数据库迁移向 `apps(id,name,description,status)` 插入一行(`id` 推荐全小写短标识,如 `cms` / `tms`)。
|
||||||
|
- 下线应用:将 `apps.status` 置为非 `active`(例如 `disabled`),之后将无法再被设置进任何租户的 enabled_apps。
|
||||||
|
- 查询可用应用(示例 SQL):`SELECT id, name, status FROM apps ORDER BY id;`
|
||||||
|
|
||||||
### Step 5:列出用户(User)
|
### Step 5:列出用户(User)
|
||||||
|
|
||||||
**GET** `/users?page=1&page_size=20`
|
**GET** `/users?page=1&page_size=20`
|
||||||
|
|
||||||
|
- Tag:`User`
|
||||||
- 必需 Header:`Authorization: Bearer <access_token>`
|
- 必需 Header:`Authorization: Bearer <access_token>`
|
||||||
- 分页规则:
|
- 分页规则:
|
||||||
- `page` 默认 1,必须 >= 1
|
- `page` 默认 1,必须 >= 1
|
||||||
@@ -205,6 +232,7 @@
|
|||||||
|
|
||||||
**GET** `/roles`
|
**GET** `/roles`
|
||||||
|
|
||||||
|
- Tag:`Role`
|
||||||
- 必需 Header:`Authorization: Bearer <access_token>`
|
- 必需 Header:`Authorization: Bearer <access_token>`
|
||||||
|
|
||||||
成功(200):
|
成功(200):
|
||||||
@@ -221,12 +249,14 @@
|
|||||||
|
|
||||||
**GET** `/users/{id}/roles`
|
**GET** `/users/{id}/roles`
|
||||||
|
|
||||||
|
- Tag:`User`
|
||||||
- Header:`Authorization: Bearer <access_token>`
|
- Header:`Authorization: Bearer <access_token>`
|
||||||
|
|
||||||
#### 7.2 设置用户角色(全量覆盖,幂等;需要 user:write)
|
#### 7.2 设置用户角色(全量覆盖,幂等;需要 user:write)
|
||||||
|
|
||||||
**PUT** `/users/{id}/roles`
|
**PUT** `/users/{id}/roles`
|
||||||
|
|
||||||
|
- Tag:`User`
|
||||||
- Header:`Authorization: Bearer <access_token>`
|
- Header:`Authorization: Bearer <access_token>`
|
||||||
- Body:
|
- Body:
|
||||||
|
|
||||||
|
|||||||
106
src/docs.rs
106
src/docs.rs
@@ -5,11 +5,61 @@ use crate::models::{
|
|||||||
UpdateTenantEnabledAppsRequest, UpdateTenantRequest, UpdateTenantStatusRequest,
|
UpdateTenantEnabledAppsRequest, UpdateTenantRequest, UpdateTenantStatusRequest,
|
||||||
UpdateUserRequest, UpdateUserRolesRequest, User, UserResponse,
|
UpdateUserRequest, UpdateUserRolesRequest, User, UserResponse,
|
||||||
};
|
};
|
||||||
|
use serde_json::Value;
|
||||||
use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme};
|
use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme};
|
||||||
use utoipa::{Modify, OpenApi};
|
use utoipa::{Modify, OpenApi};
|
||||||
|
|
||||||
struct SecurityAddon;
|
struct SecurityAddon;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct DocsEnvConfig {
|
||||||
|
default_tenant_id: Option<String>,
|
||||||
|
default_token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocsEnvConfig {
|
||||||
|
fn from_env() -> Self {
|
||||||
|
let require_tenant = std::env::var("IAM_DOCS_REQUIRE_TENANT_ID")
|
||||||
|
.map(|v| v == "1")
|
||||||
|
.unwrap_or(false);
|
||||||
|
let require_token = std::env::var("IAM_DOCS_REQUIRE_TOKEN")
|
||||||
|
.map(|v| v == "1")
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let default_tenant_id = std::env::var("IAM_DOCS_DEFAULT_TENANT_ID").ok();
|
||||||
|
let default_token = std::env::var("IAM_DOCS_DEFAULT_TOKEN").ok();
|
||||||
|
|
||||||
|
if require_tenant && default_tenant_id.is_none() {
|
||||||
|
panic!("IAM_DOCS_REQUIRE_TENANT_ID=1 but IAM_DOCS_DEFAULT_TENANT_ID is not set");
|
||||||
|
}
|
||||||
|
if require_token && default_token.is_none() {
|
||||||
|
panic!("IAM_DOCS_REQUIRE_TOKEN=1 but IAM_DOCS_DEFAULT_TOKEN is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
default_tenant_id,
|
||||||
|
default_token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tenant_example(&self) -> String {
|
||||||
|
self.default_tenant_id
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "11111111-1111-1111-1111-111111111111".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authorization_example(&self) -> String {
|
||||||
|
let Some(token) = self.default_token.clone() else {
|
||||||
|
return "Bearer <access_token>".to_string();
|
||||||
|
};
|
||||||
|
if token.to_ascii_lowercase().starts_with("bearer ") {
|
||||||
|
token
|
||||||
|
} else {
|
||||||
|
format!("Bearer {}", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Modify for SecurityAddon {
|
impl Modify for SecurityAddon {
|
||||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||||
let components = openapi
|
let components = openapi
|
||||||
@@ -24,6 +74,51 @@ impl Modify for SecurityAddon {
|
|||||||
.build(),
|
.build(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let cfg = DocsEnvConfig::from_env();
|
||||||
|
apply_header_parameter_examples(openapi, &cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_header_parameter_examples(openapi: &mut utoipa::openapi::OpenApi, cfg: &DocsEnvConfig) {
|
||||||
|
use utoipa::openapi::path::ParameterBuilder;
|
||||||
|
use utoipa::openapi::path::ParameterIn;
|
||||||
|
|
||||||
|
let tenant_value = cfg.tenant_example();
|
||||||
|
let auth_value = cfg.authorization_example();
|
||||||
|
|
||||||
|
for (_path, item) in openapi.paths.paths.iter_mut() {
|
||||||
|
let operations = [
|
||||||
|
item.get.as_mut(),
|
||||||
|
item.post.as_mut(),
|
||||||
|
item.put.as_mut(),
|
||||||
|
item.patch.as_mut(),
|
||||||
|
item.delete.as_mut(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for op in operations.into_iter().flatten() {
|
||||||
|
let Some(params) = op.parameters.as_mut() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for param in params.iter_mut() {
|
||||||
|
if param.parameter_in == ParameterIn::Header
|
||||||
|
&& param.name.eq_ignore_ascii_case("X-Tenant-ID")
|
||||||
|
{
|
||||||
|
let builder: ParameterBuilder = std::mem::take(param).into();
|
||||||
|
*param = builder
|
||||||
|
.example(Some(Value::String(tenant_value.clone())))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
if param.parameter_in == ParameterIn::Header
|
||||||
|
&& param.name.eq_ignore_ascii_case("Authorization")
|
||||||
|
{
|
||||||
|
let builder: ParameterBuilder = std::mem::take(param).into();
|
||||||
|
*param = builder
|
||||||
|
.example(Some(Value::String(auth_value.clone())))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,16 +130,7 @@ impl Modify for SecurityAddon {
|
|||||||
version = "0.1.0",
|
version = "0.1.0",
|
||||||
description = include_str!("../docs/SCALAR_GUIDE.md")
|
description = include_str!("../docs/SCALAR_GUIDE.md")
|
||||||
),
|
),
|
||||||
servers(
|
|
||||||
(
|
|
||||||
url = "https://{env}/api",
|
|
||||||
description = "Environment server",
|
|
||||||
variables(
|
|
||||||
("env" = (default = "dev", enum_values("dev", "staging", "prod"))),
|
|
||||||
("port" = (default = "5010"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
paths(
|
paths(
|
||||||
handlers::auth::register_handler,
|
handlers::auth::register_handler,
|
||||||
handlers::auth::login_handler,
|
handlers::auth::login_handler,
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use axum::{Json, extract::State};
|
|||||||
use common_telemetry::{AppError, AppResponse};
|
use common_telemetry::{AppError, AppResponse};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
/// 注册接口
|
/// Register (create user in tenant).
|
||||||
|
/// 注册接口(在租户下创建用户)。
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/auth/register",
|
path = "/auth/register",
|
||||||
@@ -40,7 +41,8 @@ pub async fn register_handler(
|
|||||||
Ok(AppResponse::created(response))
|
Ok(AppResponse::created(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 登录接口
|
/// Login (issue access token).
|
||||||
|
/// 登录接口(签发访问令牌)。
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/auth/login",
|
path = "/auth/login",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use tracing::instrument;
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// List current user's permissions in current tenant.
|
||||||
/// 查询当前登录用户在当前租户下的权限编码列表。
|
/// 查询当前登录用户在当前租户下的权限编码列表。
|
||||||
///
|
///
|
||||||
/// 用途:
|
/// 用途:
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ use common_telemetry::{AppError, AppResponse};
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
/// Get tenant enabled apps (platform scope).
|
||||||
|
/// 平台层:查询租户已开通应用(enabled_apps)。
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/platform/tenants/{tenant_id}/enabled-apps",
|
path = "/platform/tenants/{tenant_id}/enabled-apps",
|
||||||
@@ -46,6 +48,8 @@ pub async fn get_tenant_enabled_apps_handler(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set tenant enabled apps (platform scope, full replace).
|
||||||
|
/// 平台层:设置租户已开通应用(enabled_apps,全量覆盖)。
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
put,
|
put,
|
||||||
path = "/platform/tenants/{tenant_id}/enabled-apps",
|
path = "/platform/tenants/{tenant_id}/enabled-apps",
|
||||||
@@ -89,4 +93,3 @@ pub async fn set_tenant_enabled_apps_handler(
|
|||||||
updated_at,
|
updated_at,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use tracing::instrument;
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state, payload))]
|
#[instrument(skip(state, payload))]
|
||||||
|
/// Create role in current tenant.
|
||||||
/// 在当前租户下创建角色。
|
/// 在当前租户下创建角色。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -87,6 +88,7 @@ pub async fn create_role_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// List roles in current tenant.
|
||||||
/// 查询当前租户下的角色列表。
|
/// 查询当前租户下的角色列表。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use tracing::instrument;
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state, payload))]
|
#[instrument(skip(state, payload))]
|
||||||
|
/// Create tenant (public endpoint).
|
||||||
/// 创建租户(公开接口)。
|
/// 创建租户(公开接口)。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -65,6 +66,7 @@ pub async fn create_tenant_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// Get current tenant info.
|
||||||
/// 获取当前登录用户所属租户的信息。
|
/// 获取当前登录用户所属租户的信息。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -127,6 +129,7 @@ pub async fn get_tenant_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state, payload))]
|
#[instrument(skip(state, payload))]
|
||||||
|
/// Update current tenant (name / config).
|
||||||
/// 更新当前租户的基础信息(名称 / 配置)。
|
/// 更新当前租户的基础信息(名称 / 配置)。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -194,6 +197,7 @@ pub async fn update_tenant_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state, payload))]
|
#[instrument(skip(state, payload))]
|
||||||
|
/// Update current tenant status (e.g. active / disabled).
|
||||||
/// 更新当前租户状态(如 active / disabled)。
|
/// 更新当前租户状态(如 active / disabled)。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -260,6 +264,7 @@ pub async fn update_tenant_status_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// Delete current tenant.
|
||||||
/// 删除当前租户。
|
/// 删除当前租户。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub struct ListUsersQuery {
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// List users in tenant with pagination.
|
||||||
/// 分页查询当前租户下的用户列表。
|
/// 分页查询当前租户下的用户列表。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -114,6 +115,7 @@ pub async fn list_users_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// Get user by id.
|
||||||
/// 根据用户 ID 查询用户详情。
|
/// 根据用户 ID 查询用户详情。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -181,6 +183,7 @@ pub async fn get_user_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state, payload))]
|
#[instrument(skip(state, payload))]
|
||||||
|
/// Update user (currently supports updating email).
|
||||||
/// 更新指定用户信息(目前支持更新邮箱)。
|
/// 更新指定用户信息(目前支持更新邮箱)。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -250,6 +253,7 @@ pub async fn update_user_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// Delete user.
|
||||||
/// 删除指定用户。
|
/// 删除指定用户。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -312,6 +316,7 @@ pub async fn delete_user_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
/// List roles bound to a user.
|
||||||
/// 查询用户已绑定的角色列表。
|
/// 查询用户已绑定的角色列表。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
@@ -380,6 +385,7 @@ pub async fn list_user_roles_handler(
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[instrument(skip(state, payload))]
|
#[instrument(skip(state, payload))]
|
||||||
|
/// Set user's roles (full replace, idempotent).
|
||||||
/// 设置用户的角色绑定(全量覆盖,幂等)。
|
/// 设置用户的角色绑定(全量覆盖,幂等)。
|
||||||
///
|
///
|
||||||
/// 业务规则:
|
/// 业务规则:
|
||||||
|
|||||||
@@ -367,10 +367,11 @@ impl TenantService {
|
|||||||
if enabled_apps.is_empty() {
|
if enabled_apps.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let rows: Vec<String> = sqlx::query_scalar("SELECT id FROM apps WHERE id = ANY($1)")
|
let rows: Vec<String> =
|
||||||
.bind(enabled_apps)
|
sqlx::query_scalar("SELECT id FROM apps WHERE id = ANY($1) AND status = 'active'")
|
||||||
.fetch_all(&self.pool)
|
.bind(enabled_apps)
|
||||||
.await?;
|
.fetch_all(&self.pool)
|
||||||
|
.await?;
|
||||||
let found: HashSet<String> = rows.into_iter().collect();
|
let found: HashSet<String> = rows.into_iter().collect();
|
||||||
for app in enabled_apps {
|
for app in enabled_apps {
|
||||||
if !found.contains(app) {
|
if !found.contains(app) {
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ async fn tenant_enabled_apps_roundtrip_and_version_conflict()
|
|||||||
.await;
|
.await;
|
||||||
assert!(r2.is_err());
|
assert!(r2.is_err());
|
||||||
|
|
||||||
|
let r3 = tenant_service
|
||||||
|
.set_enabled_apps(tenant_id, vec!["dms".to_string()], None, actor_user_id)
|
||||||
|
.await;
|
||||||
|
assert!(r3.is_err());
|
||||||
|
|
||||||
cleanup(&pool, tenant_id, &test_app).await;
|
cleanup(&pool, tenant_id, &test_app).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user