- P3-2: ArticleEditor 图片上传接入 /upload 端点 + 封面图上传按钮 - P4-3: recover_plugins 添加 tenant 日志 + 同 ID 去重保护 - P4-4: LanguageManager 编辑弹窗改为真实表单 (name 字段) + 后端 name 持久化 - P4-6: Settings API getSetting/updateSetting 添加 encodeURIComponent
143 lines
4.3 KiB
Rust
143 lines
4.3 KiB
Rust
use axum::Extension;
|
||
use axum::extract::{FromRef, Json, Path, State};
|
||
use axum::response::Json as JsonResponse;
|
||
|
||
use erp_core::error::AppError;
|
||
use erp_core::rbac::require_permission;
|
||
use erp_core::types::{ApiResponse, Pagination, TenantContext};
|
||
|
||
use crate::config_state::ConfigState;
|
||
use crate::dto::{LanguageResp, SetSettingParams, UpdateLanguageReq};
|
||
use crate::service::setting_service::SettingService;
|
||
|
||
#[utoipa::path(
|
||
get,
|
||
path = "/api/v1/languages",
|
||
responses(
|
||
(status = 200, description = "成功", body = ApiResponse<Vec<LanguageResp>>),
|
||
(status = 401, description = "未授权"),
|
||
(status = 403, description = "权限不足"),
|
||
),
|
||
security(("bearer_auth" = [])),
|
||
tag = "语言管理"
|
||
)]
|
||
/// GET /api/v1/languages
|
||
///
|
||
/// 获取当前租户的语言配置列表。
|
||
/// 查询 scope 为 "platform" 的设置,过滤 key 以 "language." 开头的记录。
|
||
/// 需要 `language.list` 权限。
|
||
pub async fn list_languages<S>(
|
||
State(state): State<ConfigState>,
|
||
Extension(ctx): Extension<TenantContext>,
|
||
) -> Result<JsonResponse<ApiResponse<Vec<LanguageResp>>>, AppError>
|
||
where
|
||
ConfigState: FromRef<S>,
|
||
S: Clone + Send + Sync + 'static,
|
||
{
|
||
require_permission(&ctx, "language.list")?;
|
||
|
||
let pagination = Pagination {
|
||
page: Some(1),
|
||
page_size: Some(100),
|
||
};
|
||
|
||
let (settings, _total) =
|
||
SettingService::list_by_scope("platform", &None, ctx.tenant_id, &pagination, &state.db)
|
||
.await?;
|
||
|
||
let languages: Vec<LanguageResp> = settings
|
||
.into_iter()
|
||
.filter(|s| s.setting_key.starts_with("language."))
|
||
.filter_map(|s| {
|
||
let code = s.setting_key.strip_prefix("language.")?.to_string();
|
||
let name = s
|
||
.setting_value
|
||
.get("name")
|
||
.and_then(|v| v.as_str())
|
||
.unwrap_or(&code)
|
||
.to_string();
|
||
let is_active = s
|
||
.setting_value
|
||
.get("is_active")
|
||
.and_then(|v| v.as_bool())
|
||
.unwrap_or(true);
|
||
Some(LanguageResp {
|
||
code,
|
||
name,
|
||
is_active,
|
||
})
|
||
})
|
||
.collect();
|
||
|
||
Ok(JsonResponse(ApiResponse::ok(languages)))
|
||
}
|
||
|
||
#[utoipa::path(
|
||
put,
|
||
path = "/api/v1/languages/{code}",
|
||
params(("code" = String, Path, description = "语言编码")),
|
||
request_body = UpdateLanguageReq,
|
||
responses(
|
||
(status = 200, description = "成功", body = ApiResponse<LanguageResp>),
|
||
(status = 401, description = "未授权"),
|
||
(status = 403, description = "权限不足"),
|
||
),
|
||
security(("bearer_auth" = [])),
|
||
tag = "语言管理"
|
||
)]
|
||
/// PUT /api/v1/languages/:code
|
||
///
|
||
/// 更新指定语言配置的激活状态。
|
||
/// 语言配置存储在 settings 表中,key 为 "language.{code}",scope 为 "platform"。
|
||
/// 需要 `language.update` 权限。
|
||
pub async fn update_language<S>(
|
||
State(state): State<ConfigState>,
|
||
Extension(ctx): Extension<TenantContext>,
|
||
Path(code): Path<String>,
|
||
Json(req): Json<UpdateLanguageReq>,
|
||
) -> Result<JsonResponse<ApiResponse<LanguageResp>>, AppError>
|
||
where
|
||
ConfigState: FromRef<S>,
|
||
S: Clone + Send + Sync + 'static,
|
||
{
|
||
require_permission(&ctx, "language.update")?;
|
||
|
||
let key = format!("language.{}", code);
|
||
let mut value = serde_json::json!({"is_active": req.is_active});
|
||
if let Some(ref name) = req.name {
|
||
value["name"] = serde_json::Value::String(name.clone());
|
||
}
|
||
|
||
SettingService::set(
|
||
SetSettingParams {
|
||
key: key.clone(),
|
||
scope: "platform".to_string(),
|
||
scope_id: None,
|
||
value,
|
||
version: None,
|
||
},
|
||
ctx.tenant_id,
|
||
ctx.user_id,
|
||
&state.db,
|
||
&state.event_bus,
|
||
)
|
||
.await?;
|
||
|
||
// 从返回的 SettingResp 中读取实际值
|
||
let updated = SettingService::get(&key, "platform", &None, ctx.tenant_id, &state.db).await?;
|
||
|
||
// 尝试从 value 中提取 name,否则用 code 作为默认名称
|
||
let name = updated
|
||
.setting_value
|
||
.get("name")
|
||
.and_then(|v| v.as_str())
|
||
.unwrap_or(&code)
|
||
.to_string();
|
||
|
||
Ok(JsonResponse(ApiResponse::ok(LanguageResp {
|
||
code,
|
||
name,
|
||
is_active: req.is_active,
|
||
})))
|
||
}
|