mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
refactor: 拆分 system.rs 大文件,将 email_templates/proxy_errors/system_config 下沉到 shared 层
- 删除 1666 行的 admin/system/shared/system.rs,按职责拆分到独立模块 - 新增 handlers/shared/email_templates.rs 和 system_config_values.rs 存放跨层共用的模板/配置工具函数 - 新增 admin/system/shared/email_templates.rs 存放 admin 专用的模板操作逻辑 - 新增 admin/shared/proxy_errors.rs 存放 build_proxy_error_response - 清理 public/system_modules_helpers 中不属于 public 层的导出 - 新增架构测试守护模块归属边界
This commit is contained in:
@@ -8,7 +8,7 @@ pub(crate) use self::api_keys::maybe_build_local_admin_api_keys_response;
|
||||
pub(crate) use self::ldap::maybe_build_local_admin_ldap_response;
|
||||
pub(crate) use self::oauth_config::{
|
||||
build_admin_oauth_provider_payload, build_admin_oauth_supported_types_payload,
|
||||
build_admin_oauth_upsert_record, build_proxy_error_response,
|
||||
build_admin_oauth_upsert_record,
|
||||
};
|
||||
pub(crate) use self::oauth_routes::maybe_build_local_admin_oauth_response;
|
||||
pub(crate) use self::security::maybe_build_local_admin_security_response;
|
||||
|
||||
@@ -3,12 +3,7 @@ use crate::AppState;
|
||||
use aether_data::repository::oauth_providers::{
|
||||
EncryptedSecretUpdate, UpsertOAuthProviderConfigRecord,
|
||||
};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use axum::http;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
@@ -70,26 +65,6 @@ pub(crate) fn build_admin_oauth_provider_payload(
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn build_proxy_error_response(
|
||||
status: http::StatusCode,
|
||||
error_type: &str,
|
||||
message: impl Into<String>,
|
||||
details: Option<serde_json::Value>,
|
||||
) -> Response<Body> {
|
||||
let message = message.into();
|
||||
let mut error = serde_json::Map::new();
|
||||
error.insert("type".to_string(), json!(error_type));
|
||||
error.insert("message".to_string(), json!(message));
|
||||
if let Some(details) = details {
|
||||
error.insert("details".to_string(), details);
|
||||
}
|
||||
(
|
||||
status,
|
||||
Json(json!({ "error": serde_json::Value::Object(error) })),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
pub(crate) fn admin_oauth_provider_type_from_path(request_path: &str) -> Option<String> {
|
||||
let provider_type = request_path.strip_prefix("/api/admin/oauth/providers/")?;
|
||||
(!provider_type.is_empty() && !provider_type.contains('/')).then_some(provider_type.to_string())
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::oauth_config::{
|
||||
admin_oauth_provider_type_from_path, admin_oauth_test_provider_type_from_path,
|
||||
build_admin_oauth_provider_payload, build_admin_oauth_supported_types_payload,
|
||||
build_admin_oauth_upsert_record, build_proxy_error_response, AdminOAuthProviderUpsertRequest,
|
||||
build_admin_oauth_upsert_record, AdminOAuthProviderUpsertRequest,
|
||||
};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::shared::attach_admin_audit_response;
|
||||
use crate::handlers::admin::shared::{attach_admin_audit_response, build_proxy_error_response};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::async_task;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::shared::{attach_admin_audit_response, query_param_value};
|
||||
use crate::handlers::admin::shared::{
|
||||
attach_admin_audit_response, build_proxy_error_response, query_param_value,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data_contracts::repository::video_tasks::{VideoTaskQueryFilter, VideoTaskStatus};
|
||||
use axum::{
|
||||
@@ -11,7 +13,6 @@ use axum::{
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::super::super::auth::build_proxy_error_response;
|
||||
use super::builders::{
|
||||
admin_video_task_detail_id_from_path, admin_video_task_nested_id_from_path,
|
||||
admin_video_task_status_name, admin_video_task_timestamp, build_admin_video_task_list_item,
|
||||
|
||||
@@ -7,8 +7,6 @@ pub(crate) mod pool_admin;
|
||||
pub(crate) mod shared;
|
||||
pub(crate) mod write;
|
||||
|
||||
use super::auth::build_proxy_error_response;
|
||||
|
||||
mod crud;
|
||||
mod delete_task;
|
||||
mod models;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::super::build_proxy_error_response;
|
||||
use crate::handlers::admin::shared::build_proxy_error_response;
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
mod paths;
|
||||
mod payloads;
|
||||
mod proxy_errors;
|
||||
|
||||
pub(crate) use self::paths::*;
|
||||
pub(crate) use self::payloads::*;
|
||||
pub(crate) use self::proxy_errors::build_proxy_error_response;
|
||||
pub(crate) use crate::handlers::shared::{
|
||||
attach_admin_audit_response, build_admin_provider_key_response,
|
||||
decrypt_catalog_secret_with_fallbacks, default_provider_key_status_snapshot,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
pub(crate) fn build_proxy_error_response(
|
||||
status: http::StatusCode,
|
||||
error_type: &str,
|
||||
message: impl Into<String>,
|
||||
details: Option<serde_json::Value>,
|
||||
) -> Response<Body> {
|
||||
let message = message.into();
|
||||
let mut error = serde_json::Map::new();
|
||||
error.insert("type".to_string(), json!(error_type));
|
||||
error.insert("message".to_string(), json!(message));
|
||||
if let Some(details) = details {
|
||||
error.insert("details".to_string(), details);
|
||||
}
|
||||
(
|
||||
status,
|
||||
Json(json!({ "error": serde_json::Value::Object(error) })),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::super::super::auth::build_proxy_error_response;
|
||||
use super::shared::{
|
||||
admin_adaptive_adjustment_items, admin_adaptive_dispatcher_not_found_response,
|
||||
admin_adaptive_effective_limit, admin_adaptive_find_key, admin_adaptive_key_id_from_path,
|
||||
@@ -6,7 +5,9 @@ use super::shared::{
|
||||
admin_adaptive_load_candidate_keys,
|
||||
};
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::shared::{query_param_value, unix_secs_to_rfc3339};
|
||||
use crate::handlers::admin::shared::{
|
||||
build_proxy_error_response, query_param_value, unix_secs_to_rfc3339,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use super::ADMIN_AWS_REGIONS;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::auth::build_proxy_error_response;
|
||||
use crate::handlers::admin::shared::attach_admin_audit_response;
|
||||
use crate::handlers::admin::shared::build_proxy_error_response;
|
||||
use crate::handlers::admin::system::shared::{
|
||||
apply_admin_system_config_update, apply_admin_system_settings_update,
|
||||
admin_system_config_key_from_path, admin_system_email_template_preview_type_from_path,
|
||||
admin_system_email_template_reset_type_from_path, admin_system_email_template_type_from_path,
|
||||
build_admin_api_formats_payload, build_admin_system_check_update_payload,
|
||||
build_admin_system_config_detail_payload, build_admin_system_config_export_payload,
|
||||
build_admin_system_configs_payload, build_admin_system_settings_payload,
|
||||
build_admin_system_stats_payload, build_admin_system_users_export_payload,
|
||||
current_aether_version, delete_admin_system_config, is_admin_system_configs_root,
|
||||
is_admin_system_email_templates_root,
|
||||
};
|
||||
use crate::handlers::public::{
|
||||
apply_admin_email_template_update, apply_admin_system_config_update,
|
||||
apply_admin_system_settings_update, build_admin_api_formats_payload,
|
||||
build_admin_email_template_payload, build_admin_email_templates_payload,
|
||||
build_admin_system_check_update_payload, build_admin_system_config_detail_payload,
|
||||
build_admin_system_config_export_payload, build_admin_system_configs_payload,
|
||||
build_admin_system_settings_payload, build_admin_system_stats_payload,
|
||||
build_admin_system_users_export_payload, current_aether_version, delete_admin_system_config,
|
||||
is_admin_system_configs_root, is_admin_system_email_templates_root,
|
||||
preview_admin_email_template, reset_admin_email_template,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::handlers::admin::system::shared::{
|
||||
build_admin_module_validation_result, build_admin_modules_status_payload,
|
||||
AdminSetModuleEnabledRequest,
|
||||
};
|
||||
use crate::handlers::public::module_available_from_env;
|
||||
use crate::handlers::shared::module_available_from_env;
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
use crate::handlers::shared::{
|
||||
admin_email_template_definition, admin_email_template_html_key,
|
||||
admin_email_template_subject_key, read_admin_email_template_payload,
|
||||
render_admin_email_template_html, system_config_string,
|
||||
};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::body::Bytes;
|
||||
use axum::http;
|
||||
use serde_json::json;
|
||||
|
||||
pub(crate) async fn build_admin_email_templates_payload(
|
||||
state: &AppState,
|
||||
) -> Result<serde_json::Value, GatewayError> {
|
||||
let mut templates = Vec::new();
|
||||
for template_type in ["verification", "password_reset"] {
|
||||
if let Some(payload) = read_admin_email_template_payload(state, template_type).await? {
|
||||
let mut payload = payload;
|
||||
if let Some(object) = payload.as_object_mut() {
|
||||
object.remove("default_subject");
|
||||
object.remove("default_html");
|
||||
}
|
||||
templates.push(payload);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(json!({ "templates": templates }))
|
||||
}
|
||||
|
||||
pub(crate) async fn build_admin_email_template_payload(
|
||||
state: &AppState,
|
||||
template_type: &str,
|
||||
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> {
|
||||
let Some(payload) = read_admin_email_template_payload(state, template_type).await? else {
|
||||
return Ok(Err((
|
||||
http::StatusCode::NOT_FOUND,
|
||||
json!({ "detail": format!("模板类型 '{template_type}' 不存在") }),
|
||||
)));
|
||||
};
|
||||
Ok(Ok(payload))
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_admin_email_template_update(
|
||||
state: &AppState,
|
||||
template_type: &str,
|
||||
request_body: &Bytes,
|
||||
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> {
|
||||
let Some(definition) = admin_email_template_definition(template_type) else {
|
||||
return Ok(Err((
|
||||
http::StatusCode::NOT_FOUND,
|
||||
json!({ "detail": format!("模板类型 '{template_type}' 不存在") }),
|
||||
)));
|
||||
};
|
||||
let payload = match serde_json::from_slice::<serde_json::Value>(request_body) {
|
||||
Ok(serde_json::Value::Object(payload)) => payload,
|
||||
_ => {
|
||||
return Ok(Err((
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
)));
|
||||
}
|
||||
};
|
||||
let subject = match payload.get("subject") {
|
||||
Some(serde_json::Value::String(value)) => Some(value.clone()),
|
||||
Some(serde_json::Value::Null) | None => None,
|
||||
Some(_) => {
|
||||
return Ok(Err((
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
)));
|
||||
}
|
||||
};
|
||||
let html = match payload.get("html") {
|
||||
Some(serde_json::Value::String(value)) => Some(value.clone()),
|
||||
Some(serde_json::Value::Null) | None => None,
|
||||
Some(_) => {
|
||||
return Ok(Err((
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if subject.is_none() && html.is_none() {
|
||||
return Ok(Err((
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请提供 subject 或 html" }),
|
||||
)));
|
||||
}
|
||||
|
||||
let subject_key = admin_email_template_subject_key(definition.template_type);
|
||||
let html_key = admin_email_template_html_key(definition.template_type);
|
||||
|
||||
if let Some(subject) = subject {
|
||||
if subject.is_empty() {
|
||||
let _ = state.delete_system_config_value(&subject_key).await?;
|
||||
} else {
|
||||
let _ = state
|
||||
.upsert_system_config_json_value(&subject_key, &json!(subject), None)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(html) = html {
|
||||
if html.is_empty() {
|
||||
let _ = state.delete_system_config_value(&html_key).await?;
|
||||
} else {
|
||||
let _ = state
|
||||
.upsert_system_config_json_value(&html_key, &json!(html), None)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ok(json!({ "message": "模板保存成功" })))
|
||||
}
|
||||
|
||||
pub(crate) async fn preview_admin_email_template(
|
||||
state: &AppState,
|
||||
template_type: &str,
|
||||
request_body: Option<&Bytes>,
|
||||
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> {
|
||||
let Some(definition) = admin_email_template_definition(template_type) else {
|
||||
return Ok(Err((
|
||||
http::StatusCode::NOT_FOUND,
|
||||
json!({ "detail": format!("模板类型 '{template_type}' 不存在") }),
|
||||
)));
|
||||
};
|
||||
|
||||
let payload = match request_body {
|
||||
Some(bytes) => match serde_json::from_slice::<serde_json::Value>(bytes) {
|
||||
Ok(serde_json::Value::Object(payload)) => payload,
|
||||
Ok(serde_json::Value::Null) => serde_json::Map::new(),
|
||||
_ => {
|
||||
return Ok(Err((
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
json!({ "detail": "请求数据验证失败" }),
|
||||
)));
|
||||
}
|
||||
},
|
||||
None => serde_json::Map::new(),
|
||||
};
|
||||
|
||||
let resolved = read_admin_email_template_payload(state, definition.template_type)
|
||||
.await?
|
||||
.expect("validated template type should exist");
|
||||
let resolved_html = resolved["html"].as_str().unwrap_or(definition.default_html);
|
||||
let html = payload
|
||||
.get("html")
|
||||
.and_then(|value| value.as_str())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or(resolved_html);
|
||||
|
||||
let email_app_name = state
|
||||
.read_system_config_json_value("email_app_name")
|
||||
.await?;
|
||||
let smtp_from_name = state
|
||||
.read_system_config_json_value("smtp_from_name")
|
||||
.await?;
|
||||
let app_name = system_config_string(email_app_name.as_ref())
|
||||
.or_else(|| system_config_string(smtp_from_name.as_ref()))
|
||||
.unwrap_or_else(|| "Aether".to_string());
|
||||
|
||||
let mut defaults = std::collections::BTreeMap::new();
|
||||
defaults.insert("app_name".to_string(), app_name);
|
||||
defaults.insert("code".to_string(), "123456".to_string());
|
||||
defaults.insert("expire_minutes".to_string(), "30".to_string());
|
||||
defaults.insert("email".to_string(), "example@example.com".to_string());
|
||||
defaults.insert(
|
||||
"reset_link".to_string(),
|
||||
"https://example.com/reset?token=abc123".to_string(),
|
||||
);
|
||||
|
||||
let preview_variables = definition
|
||||
.variables
|
||||
.iter()
|
||||
.map(|key| {
|
||||
let value = payload
|
||||
.get(*key)
|
||||
.map(|value| match value {
|
||||
serde_json::Value::String(value) => value.clone(),
|
||||
serde_json::Value::Null => "None".to_string(),
|
||||
_ => value.to_string(),
|
||||
})
|
||||
.or_else(|| defaults.get(*key).cloned())
|
||||
.unwrap_or_else(|| format!("{{{{{key}}}}}"));
|
||||
((*key).to_string(), value)
|
||||
})
|
||||
.collect::<std::collections::BTreeMap<_, _>>();
|
||||
|
||||
let rendered_html = render_admin_email_template_html(html, &preview_variables)?;
|
||||
|
||||
Ok(Ok(json!({
|
||||
"html": rendered_html,
|
||||
"variables": preview_variables,
|
||||
})))
|
||||
}
|
||||
|
||||
pub(crate) async fn reset_admin_email_template(
|
||||
state: &AppState,
|
||||
template_type: &str,
|
||||
) -> Result<Result<serde_json::Value, (http::StatusCode, serde_json::Value)>, GatewayError> {
|
||||
let Some(definition) = admin_email_template_definition(template_type) else {
|
||||
return Ok(Err((
|
||||
http::StatusCode::NOT_FOUND,
|
||||
json!({ "detail": format!("模板类型 '{template_type}' 不存在") }),
|
||||
)));
|
||||
};
|
||||
|
||||
let _ = state
|
||||
.delete_system_config_value(&admin_email_template_subject_key(definition.template_type))
|
||||
.await?;
|
||||
let _ = state
|
||||
.delete_system_config_value(&admin_email_template_html_key(definition.template_type))
|
||||
.await?;
|
||||
|
||||
Ok(Ok(json!({
|
||||
"message": "模板已重置为默认值",
|
||||
"template": {
|
||||
"type": definition.template_type,
|
||||
"name": definition.name,
|
||||
"subject": definition.default_subject,
|
||||
"html": definition.default_html,
|
||||
}
|
||||
})))
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
mod configs;
|
||||
mod email_templates;
|
||||
mod modules;
|
||||
mod paths;
|
||||
mod settings;
|
||||
|
||||
pub(crate) use self::configs::*;
|
||||
pub(crate) use self::email_templates::{
|
||||
apply_admin_email_template_update, build_admin_email_template_payload,
|
||||
build_admin_email_templates_payload, preview_admin_email_template, reset_admin_email_template,
|
||||
};
|
||||
pub(crate) use self::modules::*;
|
||||
pub(crate) use self::paths::*;
|
||||
pub(crate) use self::settings::*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::handlers::public::{module_available_from_env, system_config_bool};
|
||||
use crate::handlers::shared::{module_available_from_env, system_config_bool};
|
||||
use crate::{AppState, GatewayError};
|
||||
use serde_json::json;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::handlers::public::{system_config_bool, system_config_string};
|
||||
use crate::handlers::shared::{system_config_bool, system_config_string};
|
||||
use crate::{AppState, GatewayError};
|
||||
use axum::body::Bytes;
|
||||
use axum::http;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user