补齐拼消消作品可见性管理
接入后台作品可见性列表中的拼消消源类型 支持后台切换拼消消已发布作品 visible 状态 补充后台前端玩法标签与公开 ReadModel 文档说明
This commit is contained in:
@@ -16,6 +16,7 @@ interface AdminWorkVisibilityPageProps {
|
|||||||
|
|
||||||
const sourceLabels: Record<string, string> = {
|
const sourceLabels: Record<string, string> = {
|
||||||
puzzle: '拼图',
|
puzzle: '拼图',
|
||||||
|
'puzzle-clear': '拼消消',
|
||||||
'custom-world': '自定义世界',
|
'custom-world': '自定义世界',
|
||||||
'jump-hop': '跳一跳',
|
'jump-hop': '跳一跳',
|
||||||
'wooden-fish': '敲木鱼',
|
'wooden-fish': '敲木鱼',
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
- `GET /admin/api/works/visibility`
|
- `GET /admin/api/works/visibility`
|
||||||
- `POST /admin/api/works/visibility`
|
- `POST /admin/api/works/visibility`
|
||||||
|
|
||||||
后台操作 key 使用统一的 `sourceType + profileId` 组合。`profileId` 在大多数玩法中对应作品 profile;特殊玩法维持既有源表身份:`big-fish` 对应 `session_id`,`bark-battle` 对应 `work_id`。`custom-world` 更新源表时必须同步 `custom_world_gallery_entry.visible`,避免兼容 gallery 缓存与统一公开 read model 出现可见性漂移。
|
后台操作 key 使用统一的 `sourceType + profileId` 组合。当前后端统一可见性管理覆盖 `puzzle`、`puzzle-clear`、`custom-world`、`jump-hop`、`wooden-fish`、`match3d`、`square-hole`、`visual-novel`、`big-fish` 和 `bark-battle`;`edutainment` 当前没有后端统一作品源表,暂不接入该后台能力。`profileId` 在大多数玩法中对应作品 profile;特殊玩法维持既有源表身份:`big-fish` 对应 `session_id`,`bark-battle` 对应 `work_id`。`custom-world` 更新源表时必须同步 `custom_world_gallery_entry.visible`,避免兼容 gallery 缓存与统一公开 read model 出现可见性漂移。
|
||||||
|
|
||||||
该后台能力只修改源表 / source view 过滤事实,不把 `visible` 暴露到公开列表或公开详情契约。隐藏作品后,统一 `public_work_gallery_entry` 与 `public_work_detail_entry` 不再返回该作品;恢复显示后重新进入公开 read model。
|
该后台能力只修改源表 / source view 过滤事实,不把 `visible` 暴露到公开列表或公开详情契约。隐藏作品后,统一 `public_work_gallery_entry` 与 `public_work_detail_entry` 不再返回该作品;恢复显示后重新进入公开 read model。
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use module_custom_world::CustomWorldPublicationStatus;
|
|||||||
use module_puzzle::PuzzlePublicationStatus;
|
use module_puzzle::PuzzlePublicationStatus;
|
||||||
|
|
||||||
const SOURCE_TYPE_PUZZLE: &str = "puzzle";
|
const SOURCE_TYPE_PUZZLE: &str = "puzzle";
|
||||||
|
const SOURCE_TYPE_PUZZLE_CLEAR: &str = "puzzle-clear";
|
||||||
const SOURCE_TYPE_CUSTOM_WORLD: &str = "custom-world";
|
const SOURCE_TYPE_CUSTOM_WORLD: &str = "custom-world";
|
||||||
const SOURCE_TYPE_JUMP_HOP: &str = "jump-hop";
|
const SOURCE_TYPE_JUMP_HOP: &str = "jump-hop";
|
||||||
const SOURCE_TYPE_WOODEN_FISH: &str = "wooden-fish";
|
const SOURCE_TYPE_WOODEN_FISH: &str = "wooden-fish";
|
||||||
@@ -63,6 +64,7 @@ fn list_work_visibility_tx(
|
|||||||
|
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
entries.extend(list_puzzle_work_visibility(ctx));
|
entries.extend(list_puzzle_work_visibility(ctx));
|
||||||
|
entries.extend(list_puzzle_clear_work_visibility(ctx));
|
||||||
entries.extend(list_custom_world_work_visibility(ctx));
|
entries.extend(list_custom_world_work_visibility(ctx));
|
||||||
entries.extend(list_jump_hop_work_visibility(ctx));
|
entries.extend(list_jump_hop_work_visibility(ctx));
|
||||||
entries.extend(list_wooden_fish_work_visibility(ctx));
|
entries.extend(list_wooden_fish_work_visibility(ctx));
|
||||||
@@ -85,6 +87,9 @@ fn update_work_visibility_tx(
|
|||||||
|
|
||||||
match source_type.as_str() {
|
match source_type.as_str() {
|
||||||
SOURCE_TYPE_PUZZLE => update_puzzle_work_visibility(ctx, &profile_id, input.visible),
|
SOURCE_TYPE_PUZZLE => update_puzzle_work_visibility(ctx, &profile_id, input.visible),
|
||||||
|
SOURCE_TYPE_PUZZLE_CLEAR => {
|
||||||
|
update_puzzle_clear_work_visibility(ctx, &profile_id, input.visible)
|
||||||
|
}
|
||||||
SOURCE_TYPE_CUSTOM_WORLD => {
|
SOURCE_TYPE_CUSTOM_WORLD => {
|
||||||
update_custom_world_work_visibility(ctx, &profile_id, input.visible)
|
update_custom_world_work_visibility(ctx, &profile_id, input.visible)
|
||||||
}
|
}
|
||||||
@@ -167,6 +172,63 @@ fn puzzle_work_visibility_snapshot(row: &PuzzleWorkProfileRow) -> AdminWorkVisib
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_puzzle_clear_work_visibility(ctx: &ReducerContext) -> Vec<AdminWorkVisibilitySnapshot> {
|
||||||
|
ctx.db
|
||||||
|
.puzzle_clear_work_profile()
|
||||||
|
.by_puzzle_clear_work_publication_status()
|
||||||
|
.filter(PUZZLE_CLEAR_PUBLICATION_PUBLISHED)
|
||||||
|
.map(|row| puzzle_clear_work_visibility_snapshot(&row))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_puzzle_clear_work_visibility(
|
||||||
|
ctx: &ReducerContext,
|
||||||
|
profile_id: &str,
|
||||||
|
visible: bool,
|
||||||
|
) -> Result<AdminWorkVisibilitySnapshot, String> {
|
||||||
|
let row = ctx
|
||||||
|
.db
|
||||||
|
.puzzle_clear_work_profile()
|
||||||
|
.profile_id()
|
||||||
|
.find(&profile_id.to_string())
|
||||||
|
.ok_or_else(|| "拼消消作品不存在".to_string())?;
|
||||||
|
if row.publication_status != PUZZLE_CLEAR_PUBLICATION_PUBLISHED {
|
||||||
|
return Err("只能修改已发布拼消消作品可见性".to_string());
|
||||||
|
}
|
||||||
|
let next = PuzzleClearWorkProfileRow { visible, ..row };
|
||||||
|
let snapshot = puzzle_clear_work_visibility_snapshot(&next);
|
||||||
|
let profile_id = next.profile_id.clone();
|
||||||
|
ctx.db
|
||||||
|
.puzzle_clear_work_profile()
|
||||||
|
.profile_id()
|
||||||
|
.delete(&profile_id);
|
||||||
|
ctx.db.puzzle_clear_work_profile().insert(next);
|
||||||
|
Ok(snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn puzzle_clear_work_visibility_snapshot(
|
||||||
|
row: &PuzzleClearWorkProfileRow,
|
||||||
|
) -> AdminWorkVisibilitySnapshot {
|
||||||
|
let sort_time = timestamp_sort_micros(row.published_at, row.updated_at);
|
||||||
|
AdminWorkVisibilitySnapshot {
|
||||||
|
source_type: SOURCE_TYPE_PUZZLE_CLEAR.to_string(),
|
||||||
|
work_id: row.work_id.clone(),
|
||||||
|
profile_id: row.profile_id.clone(),
|
||||||
|
source_session_id: Some(row.source_session_id.clone()).filter(|value| !value.is_empty()),
|
||||||
|
public_work_code: build_prefixed_public_work_code("PC", &row.profile_id),
|
||||||
|
owner_user_id: row.owner_user_id.clone(),
|
||||||
|
author_display_name: row.author_display_name.clone(),
|
||||||
|
title: choose_non_empty(&[row.work_title.as_str(), row.theme_prompt.as_str(), "拼消消"]),
|
||||||
|
subtitle: "拼消消".to_string(),
|
||||||
|
cover_image_src: Some(row.cover_image_src.clone()).filter(|value| !value.is_empty()),
|
||||||
|
visible: row.visible,
|
||||||
|
published_at_micros: row
|
||||||
|
.published_at
|
||||||
|
.map(|value| value.to_micros_since_unix_epoch()),
|
||||||
|
updated_at_micros: sort_time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn list_custom_world_work_visibility(ctx: &ReducerContext) -> Vec<AdminWorkVisibilitySnapshot> {
|
fn list_custom_world_work_visibility(ctx: &ReducerContext) -> Vec<AdminWorkVisibilitySnapshot> {
|
||||||
ctx.db
|
ctx.db
|
||||||
.custom_world_profile()
|
.custom_world_profile()
|
||||||
|
|||||||
Reference in New Issue
Block a user