e5460280ce
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
- 后端:GET /api/runtime/frontend-config 新增 gameDistributionPublishEnabled,复用 `game-distribution:publish` 判据(未配置或 enabled=false 时对已登录作者默认开放,显式收紧后只放行白名单/灰度命中用户,匿名恒为 false),前端入口与写入口共用同一事实源。 - 后端用例:新增 frontend_runtime_config_game_distribution_publish_is_scoped_to_authenticated_gate,覆盖默认开放、enabled=true 无白名单、白名单命中、deny 名单、enabled=false 回退与 rolloutPercent=100。 - 网页:平台壳按灰度隐藏「发布游戏 / 发布新版本」入口;/games/publish 直接访问时渲染「发布功能正在灰度中」并提供重新检查;读取失败按放行处理,由后端写入口把关并返回可读文案。 - AGC:新增 readGamePublishAvailability(同一运行时配置字段),只有命中才把发布回调交给 DirectProject 聊天头;字段缺失或读取失败按不开放处理。 - 文档:玩法链路、后端数据契约与实施计划记录灰度口径、入口行为与验证命令。
95 lines
3.3 KiB
Rust
95 lines
3.3 KiB
Rust
use axum::{
|
|
extract::{Extension, State},
|
|
http::{HeaderMap, StatusCode, header},
|
|
response::{IntoResponse, Response},
|
|
};
|
|
use serde::Serialize;
|
|
use serde_json::json;
|
|
|
|
use crate::{
|
|
api_response::json_success_body, auth::optional_access_token_from_headers,
|
|
http_error::AppError, request_context::RequestContext, state::AppState,
|
|
};
|
|
|
|
#[derive(Debug, Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct FrontendRuntimeConfigResponse {
|
|
pub image_editor_agent_sidebar_enabled: bool,
|
|
pub agc_template_library_enabled: bool,
|
|
/// 游戏发布灰度:运营未配置 `game-distribution:publish` 时对已登录作者默认开放,
|
|
/// 显式收紧后只有白名单/灰度命中的作者拿到 `true`;匿名一律 `false`(发布必须先登录)。
|
|
pub game_distribution_publish_enabled: bool,
|
|
}
|
|
|
|
pub async fn get_frontend_runtime_config(
|
|
State(state): State<AppState>,
|
|
Extension(request_context): Extension<RequestContext>,
|
|
headers: HeaderMap,
|
|
) -> Result<Response, AppError> {
|
|
let authenticated = optional_access_token_from_headers(
|
|
&state,
|
|
"/api/runtime/frontend-config".to_string(),
|
|
headers,
|
|
request_context.request_id().to_string(),
|
|
)
|
|
.await?;
|
|
let user_id = authenticated
|
|
.as_ref()
|
|
.map(|authenticated| authenticated.claims().user_id());
|
|
let image_editor_agent_sidebar_enabled = state
|
|
.is_image_editor_agent_sidebar_enabled_for_user(user_id)
|
|
.await
|
|
.map_err(|error| {
|
|
AppError::from_status(StatusCode::BAD_GATEWAY)
|
|
.with_message("读取前端运行时配置失败")
|
|
.with_details(json!({
|
|
"provider": "spacetimedb",
|
|
"message": error.to_string(),
|
|
}))
|
|
})?;
|
|
|
|
let agc_template_library_enabled = state
|
|
.is_agc_template_library_enabled_for_user(user_id)
|
|
.await
|
|
.map_err(|error| {
|
|
AppError::from_status(StatusCode::BAD_GATEWAY)
|
|
.with_message("读取前端运行时配置失败")
|
|
.with_details(json!({
|
|
"provider": "spacetimedb",
|
|
"message": error.to_string(),
|
|
}))
|
|
})?;
|
|
|
|
let game_distribution_publish_enabled = match user_id {
|
|
Some(user_id) => state
|
|
.is_game_distribution_publish_enabled_for_user(Some(user_id))
|
|
.await
|
|
.map_err(|error| {
|
|
AppError::from_status(StatusCode::BAD_GATEWAY)
|
|
.with_message("读取前端运行时配置失败")
|
|
.with_details(json!({
|
|
"provider": "spacetimedb",
|
|
"message": error.to_string(),
|
|
}))
|
|
})?,
|
|
// 未登录时发布入口不开放:灰度只面向已登录作者,写入口也要求 Bearer 会话。
|
|
None => false,
|
|
};
|
|
|
|
Ok((
|
|
[
|
|
(header::CACHE_CONTROL, "no-store"),
|
|
(header::VARY, "Authorization"),
|
|
],
|
|
json_success_body(
|
|
Some(&request_context),
|
|
FrontendRuntimeConfigResponse {
|
|
image_editor_agent_sidebar_enabled,
|
|
agc_template_library_enabled,
|
|
game_distribution_publish_enabled,
|
|
},
|
|
),
|
|
)
|
|
.into_response())
|
|
}
|