合并 master 并修复架构分支回归
合入 master 最新的认证、玩法契约与推荐页改动。 修复拼图草稿生成、推荐页下一关和公开详情访客试玩回归。 修复抓大鹅草稿试玩鉴权与首屏推荐详情测试入口。 补齐相关测试夹具、文档与团队记忆更新。
This commit is contained in:
@@ -2640,6 +2640,7 @@ mod tests {
|
||||
login_payload["bindingStatus"],
|
||||
Value::String("pending_bind_phone".to_string())
|
||||
);
|
||||
assert_eq!(login_payload["created"], Value::Bool(true));
|
||||
assert_eq!(
|
||||
login_payload["user"]["loginMethod"],
|
||||
Value::String("wechat".to_string())
|
||||
@@ -2746,6 +2747,7 @@ mod tests {
|
||||
login_payload["bindingStatus"],
|
||||
Value::String("pending_bind_phone".to_string())
|
||||
);
|
||||
assert_eq!(login_payload["created"], Value::Bool(true));
|
||||
|
||||
let bind_response = app
|
||||
.oneshot(
|
||||
@@ -4423,4 +4425,4 @@ mod tests {
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND, "{path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ use serde_json::{Value, json};
|
||||
use shared_contracts::jump_hop::{
|
||||
JumpHopActionRequest, JumpHopActionType, JumpHopCharacterAsset, JumpHopDraftResponse,
|
||||
JumpHopGalleryDetailResponse, JumpHopGenerationStatus, JumpHopJumpRequest, JumpHopJumpResponse,
|
||||
JumpHopLeaderboardResponse, JumpHopRestartRunRequest, JumpHopRunResponse,
|
||||
JumpHopLeaderboardEntry, JumpHopLeaderboardResponse, JumpHopRestartRunRequest,
|
||||
JumpHopRunResponse,
|
||||
JumpHopSessionResponse, JumpHopSessionSnapshotResponse, JumpHopStartRunRequest,
|
||||
JumpHopTileAsset, JumpHopTileType, JumpHopWorkDetailResponse, JumpHopWorkMutationResponse,
|
||||
JumpHopWorksResponse, JumpHopWorkspaceCreateRequest,
|
||||
@@ -222,6 +223,34 @@ pub async fn list_jump_hop_works(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_jump_hop_work_detail(
|
||||
State(state): State<AppState>,
|
||||
Path(profile_id): Path<String>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
ensure_non_empty(&request_context, &profile_id, "profileId")?;
|
||||
let work = state
|
||||
.spacetime_client()
|
||||
.get_jump_hop_work_profile(
|
||||
profile_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
jump_hop_error_response(
|
||||
&request_context,
|
||||
JUMP_HOP_CREATION_PROVIDER,
|
||||
map_jump_hop_client_error(error),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
JumpHopWorkDetailResponse { item: work },
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn delete_jump_hop_work(
|
||||
State(state): State<AppState>,
|
||||
Path(profile_id): Path<String>,
|
||||
@@ -231,7 +260,10 @@ pub async fn delete_jump_hop_work(
|
||||
ensure_non_empty(&request_context, &profile_id, "profileId")?;
|
||||
let works = state
|
||||
.spacetime_client()
|
||||
.delete_jump_hop_work(profile_id, authenticated.claims().user_id().to_string())
|
||||
.delete_jump_hop_work(
|
||||
profile_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
jump_hop_error_response(
|
||||
@@ -296,8 +328,14 @@ pub async fn get_jump_hop_leaderboard(
|
||||
Some(&request_context),
|
||||
JumpHopLeaderboardResponse {
|
||||
profile_id: leaderboard.profile_id,
|
||||
items: leaderboard.items,
|
||||
viewer_best: leaderboard.viewer_best,
|
||||
items: leaderboard
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|entry| resolve_jump_hop_leaderboard_entry_display_name(&state, entry))
|
||||
.collect(),
|
||||
viewer_best: leaderboard
|
||||
.viewer_best
|
||||
.map(|entry| resolve_jump_hop_leaderboard_entry_display_name(&state, entry)),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -1270,6 +1308,51 @@ fn build_jump_hop_work_play_tracking_draft(
|
||||
WorkPlayTrackingDraft::runtime_principal("jump-hop", work_id, principal, source_route)
|
||||
}
|
||||
|
||||
fn resolve_jump_hop_leaderboard_entry_display_name(
|
||||
state: &AppState,
|
||||
mut entry: JumpHopLeaderboardEntry,
|
||||
) -> JumpHopLeaderboardEntry {
|
||||
entry.display_name = resolve_jump_hop_leaderboard_display_name(state, &entry.player_id);
|
||||
entry
|
||||
}
|
||||
|
||||
fn resolve_jump_hop_leaderboard_display_name(state: &AppState, player_id: &str) -> String {
|
||||
resolve_jump_hop_leaderboard_display_name_with_lookup(player_id, |user_id| {
|
||||
state
|
||||
.auth_user_service()
|
||||
.get_user_by_id(user_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|user| normalize_non_empty_text(user.display_name.as_str()))
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_jump_hop_leaderboard_display_name_with_lookup(
|
||||
player_id: &str,
|
||||
lookup_display_name: impl FnOnce(&str) -> Option<String>,
|
||||
) -> String {
|
||||
let player_id = player_id.trim();
|
||||
if player_id.is_empty() {
|
||||
return "玩家".to_string();
|
||||
}
|
||||
if player_id.starts_with("guest-runtime-") {
|
||||
return "游客玩家".to_string();
|
||||
}
|
||||
|
||||
lookup_display_name(player_id)
|
||||
.and_then(|display_name| normalize_non_empty_text(display_name.as_str()))
|
||||
.unwrap_or_else(|| "失效玩家".to_string())
|
||||
}
|
||||
|
||||
fn normalize_non_empty_text(value: &str) -> Option<String> {
|
||||
let value = value.trim();
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_jump_hop_draft_runtime_mode(runtime_mode: &str) -> bool {
|
||||
runtime_mode.trim().eq_ignore_ascii_case("draft")
|
||||
}
|
||||
@@ -1464,6 +1547,33 @@ mod tests {
|
||||
assert!(!is_jump_hop_draft_runtime_mode(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_hop_leaderboard_display_name_never_falls_back_to_player_id() {
|
||||
assert_eq!(
|
||||
resolve_jump_hop_leaderboard_display_name_with_lookup(" user-secret-1 ", |user_id| {
|
||||
assert_eq!(user_id, "user-secret-1");
|
||||
Some(" 陶泥儿玩家 ".to_string())
|
||||
}),
|
||||
"陶泥儿玩家"
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_jump_hop_leaderboard_display_name_with_lookup("guest-runtime-1", |_| {
|
||||
panic!("guest player should not query account display name")
|
||||
}),
|
||||
"游客玩家"
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_jump_hop_leaderboard_display_name_with_lookup("user-missing", |_| None),
|
||||
"失效玩家"
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_jump_hop_leaderboard_display_name_with_lookup("", |_| {
|
||||
panic!("empty player id should not query account display name")
|
||||
}),
|
||||
"玩家"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_hop_tile_atlas_prompt_uses_dedicated_five_by_five_floor_layout() {
|
||||
let prompt = build_jump_hop_tile_atlas_prompt("森林冒险", "森林主题清爽游戏化立体感平台");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::{
|
||||
middleware,
|
||||
routing::{delete, get, post},
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
|
||||
@@ -9,8 +9,9 @@ use crate::{
|
||||
jump_hop::{
|
||||
create_jump_hop_session, delete_jump_hop_work, execute_jump_hop_action,
|
||||
get_jump_hop_gallery_detail, get_jump_hop_leaderboard, get_jump_hop_runtime_work,
|
||||
get_jump_hop_session, jump_hop_run_jump, list_jump_hop_gallery, list_jump_hop_works,
|
||||
publish_jump_hop_work, restart_jump_hop_run, start_jump_hop_run,
|
||||
get_jump_hop_session, get_jump_hop_work_detail, jump_hop_run_jump,
|
||||
list_jump_hop_gallery, list_jump_hop_works, publish_jump_hop_work, restart_jump_hop_run,
|
||||
start_jump_hop_run,
|
||||
},
|
||||
state::AppState,
|
||||
};
|
||||
@@ -47,10 +48,12 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
)
|
||||
.route(
|
||||
"/api/creation/jump-hop/works/{profile_id}",
|
||||
delete(delete_jump_hop_work).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
get(get_jump_hop_work_detail)
|
||||
.delete(delete_jump_hop_work)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/creation/jump-hop/works/{profile_id}/publish",
|
||||
|
||||
@@ -349,6 +349,7 @@ pub async fn login_wechat_mini_program(
|
||||
token: signed_session.access_token,
|
||||
binding_status: result.user.binding_status.as_str().to_string(),
|
||||
user: map_auth_user_payload(result.user),
|
||||
created: result.created,
|
||||
},
|
||||
),
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user