修复隐藏拼图作品进入通关推荐

收口拼图公开消费路径的 Published + visible 判断

拦截隐藏拼图的公开详情、互动和正式运行态入口

补充隐藏拼图推荐候选回归测试

更新后端契约文档和团队踩坑记录
This commit is contained in:
2026-06-15 22:42:38 +08:00
parent a51e63415f
commit 767da0164a
3 changed files with 90 additions and 10 deletions

View File

@@ -153,7 +153,7 @@ pub fn puzzle_gallery_view(ctx: &AnonymousViewContext) -> Vec<PuzzleWorkProfile>
.puzzle_work_profile()
.by_puzzle_work_publication_status()
.filter(PuzzlePublicationStatus::Published)
.filter(|row| row.visible)
.filter(is_public_visible_puzzle_work)
.filter_map(
|row| match build_puzzle_work_profile_from_row_without_recent_count(&row) {
Ok(profile) => Some(profile),
@@ -183,7 +183,7 @@ pub fn puzzle_gallery_card_view(ctx: &AnonymousViewContext) -> Vec<PuzzleGallery
.puzzle_work_profile()
.by_puzzle_work_publication_status()
.filter(PuzzlePublicationStatus::Published)
.filter(|row| row.visible)
.filter(is_public_visible_puzzle_work)
.filter_map(|row| match build_puzzle_gallery_card_view_row(&row) {
Ok(item) => Some(item),
Err(error) => {
@@ -2069,6 +2069,7 @@ fn list_puzzle_gallery_tx(ctx: &TxContext) -> Result<Vec<PuzzleWorkProfile>, Str
.puzzle_work_profile()
.by_puzzle_work_publication_status()
.filter(PuzzlePublicationStatus::Published)
.filter(is_public_visible_puzzle_work)
.collect::<Vec<_>>();
let profile_ids = rows
.iter()
@@ -2094,8 +2095,8 @@ fn get_puzzle_gallery_detail_tx(
.profile_id()
.find(&input.profile_id)
.ok_or_else(|| "拼图作品不存在".to_string())?;
if row.publication_status != PuzzlePublicationStatus::Published {
return Err("拼图作品尚未发布".to_string());
if !is_public_visible_puzzle_work(&row) {
return Err("拼图作品不可公开访问".to_string());
}
build_puzzle_work_profile_from_row_with_recent_count(
ctx,
@@ -2118,7 +2119,7 @@ fn record_puzzle_work_like_tx(
.puzzle_work_profile()
.profile_id()
.find(&profile_id.to_string())
.filter(|row| row.publication_status == PuzzlePublicationStatus::Published)
.filter(is_public_visible_puzzle_work)
.ok_or_else(|| "拼图已发布作品不存在,无法点赞".to_string())?;
let inserted_like = record_public_work_like(
ctx,
@@ -2214,7 +2215,7 @@ fn remix_puzzle_work_tx(
.puzzle_work_profile()
.profile_id()
.find(&source_profile_id.to_string())
.filter(|row| row.publication_status == PuzzlePublicationStatus::Published)
.filter(is_public_visible_puzzle_work)
.ok_or_else(|| "拼图已发布源作品不存在".to_string())?;
let source_profile = build_puzzle_work_profile_from_row(&source)?;
let remixed_at = Timestamp::from_micros_since_unix_epoch(input.remixed_at_micros);
@@ -2355,6 +2356,11 @@ fn start_puzzle_run_tx(
{
return Err("入口拼图作品未发布".to_string());
}
if entry_profile_row.publication_status == PuzzlePublicationStatus::Published
&& !entry_profile_row.visible
{
return Err("入口拼图作品不可公开访问".to_string());
}
let mut entry_profile = build_puzzle_work_profile_from_row(&entry_profile_row)?;
if entry_profile.cover_image_src.is_none() {
return Err("入口拼图作品缺少正式图片".to_string());
@@ -2387,7 +2393,7 @@ fn start_puzzle_run_tx(
);
refresh_next_level_handoff(ctx, &mut run)?;
if entry_profile_row.publication_status == PuzzlePublicationStatus::Published {
if is_public_visible_puzzle_work(&entry_profile_row) {
record_public_work_play(
ctx,
PublicWorkPlayRecordInput {
@@ -2595,6 +2601,7 @@ fn advance_puzzle_next_level_tx(
.puzzle_work_profile()
.profile_id()
.find(&next_profile.profile_id)
.filter(is_public_visible_puzzle_work)
{
record_public_work_play(
ctx,
@@ -2822,7 +2829,7 @@ fn submit_puzzle_leaderboard_entry_tx(
if !matches_service_level && !is_frontend_puzzle_level_candidate(&run, &input.profile_id) {
return Err("提交成绩的拼图作品与当前关卡不匹配".to_string());
}
if current_profile_row.publication_status != PuzzlePublicationStatus::Published {
if !is_public_visible_puzzle_work(&current_profile_row) {
hydrate_puzzle_leaderboard_entries(
ctx,
&mut run,
@@ -3832,10 +3839,15 @@ fn list_published_puzzle_profiles(ctx: &TxContext) -> Result<Vec<PuzzleWorkProfi
.puzzle_work_profile()
.by_puzzle_work_publication_status()
.filter(PuzzlePublicationStatus::Published)
.filter(is_public_visible_puzzle_work)
.map(|row| build_puzzle_work_profile_from_row(&row))
.collect()
}
fn is_public_visible_puzzle_work(row: &PuzzleWorkProfileRow) -> bool {
row.publication_status == PuzzlePublicationStatus::Published && row.visible
}
fn reset_next_level_handoff(run: &mut PuzzleRunSnapshot) {
run.recommended_next_profile_id = None;
run.next_level_mode = PUZZLE_NEXT_LEVEL_MODE_NONE.to_string();
@@ -4250,6 +4262,29 @@ mod tests {
);
}
#[test]
fn hidden_published_puzzle_work_is_not_public_visible_candidate() {
let visible_published = puzzle_work_profile_row_for_visibility(
"visible-published",
PuzzlePublicationStatus::Published,
true,
);
let hidden_published = puzzle_work_profile_row_for_visibility(
"hidden-published",
PuzzlePublicationStatus::Published,
false,
);
let visible_draft = puzzle_work_profile_row_for_visibility(
"visible-draft",
PuzzlePublicationStatus::Draft,
true,
);
assert!(is_public_visible_puzzle_work(&visible_published));
assert!(!is_public_visible_puzzle_work(&hidden_published));
assert!(!is_public_visible_puzzle_work(&visible_draft));
}
#[test]
fn level_generation_failure_only_marks_target_level_failed() {
let anchor_pack = infer_anchor_pack("画面描述:一只猫在雨夜灯牌下回头。", None);
@@ -4371,4 +4406,39 @@ mod tests {
assert_eq!(entries[1].nickname, "玩家 B");
assert_eq!(entries[1].rank, 2);
}
fn puzzle_work_profile_row_for_visibility(
profile_id: &str,
publication_status: PuzzlePublicationStatus,
visible: bool,
) -> PuzzleWorkProfileRow {
let timestamp = Timestamp::from_micros_since_unix_epoch(1);
PuzzleWorkProfileRow {
profile_id: profile_id.to_string(),
work_id: format!("work-{profile_id}"),
owner_user_id: "owner".to_string(),
source_session_id: None,
author_display_name: "作者".to_string(),
work_title: "作品".to_string(),
work_description: String::new(),
level_name: "第一关".to_string(),
summary: "摘要".to_string(),
theme_tags_json: "[]".to_string(),
cover_image_src: Some("/cover.png".to_string()),
cover_asset_id: Some("asset-cover".to_string()),
levels_json: "[]".to_string(),
publication_status,
play_count: 0,
anchor_pack_json: serialize_json(&empty_anchor_pack()),
publish_ready: true,
created_at: timestamp,
updated_at: timestamp,
published_at: Some(timestamp),
remix_count: 0,
like_count: 0,
point_incentive_total_half_points: 0,
point_incentive_claimed_points: 0,
visible,
}
}
}