merge: database backed creation entry config

# Conflicts:
#	src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
This commit is contained in:
2026-05-11 11:25:35 +08:00
37 changed files with 1458 additions and 204 deletions

View File

@@ -50,6 +50,9 @@ use crate::{
generate_character_visual, get_character_visual_job, publish_character_visual,
},
creation_agent_document_input::parse_creation_agent_document_input,
creation_entry_config::{
get_creation_entry_config_handler, require_creation_entry_route_enabled,
},
creative_agent::{
cancel_creative_agent_session, confirm_creative_puzzle_template,
create_creative_agent_session, get_creative_agent_session, stream_creative_agent_message,
@@ -612,6 +615,10 @@ pub fn build_router(state: AppState) -> Router {
require_bearer_auth,
)),
)
.route(
"/api/creation-entry/config",
get(get_creation_entry_config_handler),
)
.route(
"/api/runtime/settings",
get(get_runtime_settings)
@@ -1493,6 +1500,11 @@ pub fn build_router(state: AppState) -> Router {
)),
)
.route("/api/auth/password/reset", post(reset_password))
// 后端 runtime/API 路由读取入口配置做统一熔断,避免前端隐藏后后端仍可被直接访问。
.layer(middleware::from_fn_with_state(
state.clone(),
require_creation_entry_route_enabled,
))
// 错误归一化层放在 tracing 里侧,让 tracing 记录到最终对外返回的状态与错误体形态。
.layer(middleware::from_fn(normalize_error_response))
// 响应头回写放在错误归一化外侧,确保最终写回的是归一化后的最终响应。
@@ -1953,6 +1965,28 @@ mod tests {
);
}
#[tokio::test]
async fn creation_entry_route_disabled_returns_service_unavailable() {
let state = AppState::new(AppConfig::default()).expect("state should build");
state.set_test_creation_entry_route_enabled("puzzle", false);
let app = build_router(state);
let response = app
.oneshot(
Request::builder()
.uri("/api/runtime/puzzle/works")
.body(Body::empty())
.expect("request should build"),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
let body = read_json_response(response).await;
assert_eq!(body["error"]["details"]["reason"], "creation_entry_disabled");
assert_eq!(body["error"]["details"]["creationTypeId"], "puzzle");
}
#[tokio::test]
async fn healthz_returns_standard_envelope_when_requested() {
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));