fix: polish bark battle creation flow
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use crate::*;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use sha2::{Digest, Sha256};
|
||||
use spacetimedb::AnonymousViewContext;
|
||||
|
||||
pub(crate) mod tables;
|
||||
mod types;
|
||||
@@ -8,6 +9,38 @@ mod types;
|
||||
pub use tables::*;
|
||||
pub use types::*;
|
||||
|
||||
/// Bark Battle 公开广场列表投影。
|
||||
///
|
||||
/// HTTP gallery 订阅该 public view 后读取本地 cache;view 只从已发布配置和统计投影
|
||||
/// 组装 v1 公开字段,避免每个公开列表请求重新调用 procedure 热路径。
|
||||
#[spacetimedb::view(accessor = bark_battle_gallery_view, public)]
|
||||
pub fn bark_battle_gallery_view(ctx: &AnonymousViewContext) -> Vec<BarkBattleGalleryViewRow> {
|
||||
let mut items = ctx
|
||||
.db
|
||||
.bark_battle_published_config()
|
||||
.by_bark_battle_published_owner_user_id()
|
||||
.filter(""..)
|
||||
.filter_map(|row| match build_bark_battle_gallery_view_row(ctx, &row) {
|
||||
Ok(item) => Some(item),
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
"汪汪声浪公开广场 view 跳过损坏的作品投影 work_id={}: {}",
|
||||
row.work_id,
|
||||
error
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.updated_at_micros
|
||||
.cmp(&left.updated_at_micros)
|
||||
.then_with(|| left.work_id.cmp(&right.work_id))
|
||||
});
|
||||
items
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn create_bark_battle_draft(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -106,21 +139,23 @@ fn create_bark_battle_draft_tx(
|
||||
let config = BarkBattleEditorConfigSnapshot {
|
||||
title: normalize_title(input.title.as_deref())?,
|
||||
description: normalize_optional_text(input.description.as_deref()),
|
||||
theme_preset: normalize_required_preset(&input.theme_preset, "theme_preset")?,
|
||||
player_dog_skin_preset: normalize_required_preset(
|
||||
&input.player_dog_skin_preset,
|
||||
"player_dog_skin_preset",
|
||||
theme_description: normalize_required_description(
|
||||
&input.theme_description,
|
||||
"theme_description",
|
||||
)?,
|
||||
opponent_dog_skin_preset: normalize_required_preset(
|
||||
&input.opponent_dog_skin_preset,
|
||||
"opponent_dog_skin_preset",
|
||||
player_image_description: normalize_required_description(
|
||||
&input.player_image_description,
|
||||
"player_image_description",
|
||||
)?,
|
||||
opponent_image_description: normalize_required_description(
|
||||
&input.opponent_image_description,
|
||||
"opponent_image_description",
|
||||
)?,
|
||||
onomatopoeia: Vec::new(),
|
||||
player_character_image_src: None,
|
||||
opponent_character_image_src: None,
|
||||
ui_background_image_src: None,
|
||||
bark_sound_src: None,
|
||||
difficulty_preset: normalize_difficulty(input.difficulty_preset.as_deref())?,
|
||||
leaderboard_enabled: input.leaderboard_enabled.unwrap_or(true),
|
||||
};
|
||||
let row = BarkBattleDraftConfigRow {
|
||||
draft_id: input.draft_id.clone(),
|
||||
@@ -129,7 +164,7 @@ fn create_bark_battle_draft_tx(
|
||||
config_version: 1,
|
||||
ruleset_version: BARK_BATTLE_DEFAULT_RULESET_VERSION.to_string(),
|
||||
difficulty_preset: config.difficulty_preset.clone(),
|
||||
leaderboard_enabled: config.leaderboard_enabled,
|
||||
leaderboard_enabled: true,
|
||||
config_json: to_json_string(&config),
|
||||
editor_state_json: normalize_json_string(
|
||||
input.editor_state_json.as_deref(),
|
||||
@@ -151,10 +186,8 @@ fn update_bark_battle_draft_config_tx(
|
||||
require_non_empty(&input.work_id, "bark_battle work_id")?;
|
||||
let mut editor_config = parse_editor_config(&input.config_json)?;
|
||||
normalize_editor_config_snapshot(&mut editor_config)?;
|
||||
if editor_config.difficulty_preset != input.difficulty_preset
|
||||
|| editor_config.leaderboard_enabled != input.leaderboard_enabled
|
||||
{
|
||||
return Err("bark_battle config_json 与行字段不一致".to_string());
|
||||
if editor_config.difficulty_preset != input.difficulty_preset {
|
||||
return Err("bark_battle config_json 与 difficulty_preset 不匹配".to_string());
|
||||
}
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(input.updated_at_micros);
|
||||
let existing = ctx
|
||||
@@ -166,14 +199,14 @@ fn update_bark_battle_draft_config_tx(
|
||||
if existing.owner_user_id != input.owner_user_id || existing.work_id != input.work_id {
|
||||
return Err("bark_battle draft owner/work 不匹配".to_string());
|
||||
}
|
||||
if input.config_version <= existing.config_version {
|
||||
return Err("bark_battle draft config_version 必须递增".to_string());
|
||||
}
|
||||
let mut row = existing;
|
||||
row.config_version = input.config_version;
|
||||
// 中文注释:HTTP BFF 会先读缓存再发更新,订阅缓存可能短暂落后;
|
||||
// 这里按“至少递增 1”兜底,避免前端重复保存素材时被版本号误伤。
|
||||
row.config_version = input
|
||||
.config_version
|
||||
.max(row.config_version.saturating_add(1));
|
||||
row.ruleset_version = normalize_ruleset_version(&input.ruleset_version)?;
|
||||
row.difficulty_preset = normalize_difficulty(Some(&input.difficulty_preset))?;
|
||||
row.leaderboard_enabled = input.leaderboard_enabled;
|
||||
row.config_json = to_json_string(&editor_config);
|
||||
row.updated_at = updated_at;
|
||||
ctx.db
|
||||
@@ -200,6 +233,20 @@ fn publish_bark_battle_work_tx(
|
||||
if draft.owner_user_id != input.owner_user_id || draft.work_id != input.work_id {
|
||||
return Err("bark_battle draft owner/work 不匹配".to_string());
|
||||
}
|
||||
let published_snapshot_json = match input.published_snapshot_json.as_deref() {
|
||||
Some(value) => {
|
||||
let mut editor_config = parse_editor_config(value)?;
|
||||
normalize_editor_config_snapshot(&mut editor_config)?;
|
||||
if editor_config.difficulty_preset != draft.difficulty_preset {
|
||||
return Err(
|
||||
"bark_battle published_snapshot_json 与草稿 difficulty_preset 不匹配"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
to_json_string(&editor_config)
|
||||
}
|
||||
None => draft.config_json.clone(),
|
||||
};
|
||||
let published = BarkBattlePublishedConfigRow {
|
||||
work_id: draft.work_id.clone(),
|
||||
owner_user_id: draft.owner_user_id.clone(),
|
||||
@@ -207,12 +254,9 @@ fn publish_bark_battle_work_tx(
|
||||
config_version: draft.config_version,
|
||||
ruleset_version: normalize_ruleset_version(&draft.ruleset_version)?,
|
||||
difficulty_preset: normalize_difficulty(Some(&draft.difficulty_preset))?,
|
||||
leaderboard_enabled: draft.leaderboard_enabled,
|
||||
config_json: draft.config_json.clone(),
|
||||
published_snapshot_json: match input.published_snapshot_json.as_deref() {
|
||||
Some(value) => normalize_json_string(Some(value), "published_snapshot_json")?,
|
||||
None => draft.config_json.clone(),
|
||||
},
|
||||
leaderboard_enabled: true,
|
||||
config_json: published_snapshot_json.clone(),
|
||||
published_snapshot_json,
|
||||
created_at: published_at,
|
||||
updated_at: published_at,
|
||||
published_at,
|
||||
@@ -297,7 +341,7 @@ fn start_bark_battle_run_tx(
|
||||
config_version: input.config_version,
|
||||
ruleset_version: input.ruleset_version,
|
||||
difficulty_preset: input.difficulty_preset,
|
||||
leaderboard_enabled: published.leaderboard_enabled,
|
||||
leaderboard_enabled: true,
|
||||
status: BARK_BATTLE_RUN_RUNNING.to_string(),
|
||||
client_started_at_micros: input.client_started_at_micros,
|
||||
server_started_at: started_at,
|
||||
@@ -483,7 +527,6 @@ fn draft_snapshot(row: &BarkBattleDraftConfigRow) -> BarkBattleDraftConfigSnapsh
|
||||
config_version: row.config_version,
|
||||
ruleset_version: row.ruleset_version.clone(),
|
||||
difficulty_preset: row.difficulty_preset.clone(),
|
||||
leaderboard_enabled: row.leaderboard_enabled,
|
||||
config_json: row.config_json.clone(),
|
||||
editor_state_json: row.editor_state_json.clone(),
|
||||
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
|
||||
@@ -499,7 +542,6 @@ fn runtime_config_snapshot(row: &BarkBattlePublishedConfigRow) -> BarkBattleRunt
|
||||
config_version: row.config_version,
|
||||
ruleset_version: row.ruleset_version.clone(),
|
||||
difficulty_preset: row.difficulty_preset.clone(),
|
||||
leaderboard_enabled: row.leaderboard_enabled,
|
||||
config_json: row.config_json.clone(),
|
||||
published_snapshot_json: row.published_snapshot_json.clone(),
|
||||
published_at_micros: row.published_at.to_micros_since_unix_epoch(),
|
||||
@@ -507,6 +549,43 @@ fn runtime_config_snapshot(row: &BarkBattlePublishedConfigRow) -> BarkBattleRunt
|
||||
}
|
||||
}
|
||||
|
||||
fn build_bark_battle_gallery_view_row(
|
||||
ctx: &AnonymousViewContext,
|
||||
row: &BarkBattlePublishedConfigRow,
|
||||
) -> Result<BarkBattleGalleryViewRow, String> {
|
||||
let mut editor_config = parse_editor_config(&row.config_json)?;
|
||||
normalize_editor_config_snapshot(&mut editor_config)?;
|
||||
let stats = ctx
|
||||
.db
|
||||
.bark_battle_work_stats_projection()
|
||||
.work_id()
|
||||
.find(&row.work_id);
|
||||
Ok(BarkBattleGalleryViewRow {
|
||||
work_id: row.work_id.clone(),
|
||||
owner_user_id: row.owner_user_id.clone(),
|
||||
source_draft_id: row.source_draft_id.clone(),
|
||||
config_version: row.config_version,
|
||||
ruleset_version: row.ruleset_version.clone(),
|
||||
difficulty_preset: row.difficulty_preset.clone(),
|
||||
title: editor_config.title,
|
||||
description: editor_config.description,
|
||||
theme_description: editor_config.theme_description,
|
||||
player_image_description: editor_config.player_image_description,
|
||||
opponent_image_description: editor_config.opponent_image_description,
|
||||
onomatopoeia: editor_config.onomatopoeia,
|
||||
player_character_image_src: editor_config.player_character_image_src,
|
||||
opponent_character_image_src: editor_config.opponent_character_image_src,
|
||||
ui_background_image_src: editor_config.ui_background_image_src,
|
||||
play_count: stats.as_ref().map(|stats| stats.play_count).unwrap_or(0),
|
||||
finish_count: stats
|
||||
.as_ref()
|
||||
.map(|stats| stats.finished_count)
|
||||
.unwrap_or(0),
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
published_at_micros: row.published_at.to_micros_since_unix_epoch(),
|
||||
})
|
||||
}
|
||||
|
||||
fn hash_run_token(token: &str) -> String {
|
||||
let digest = Sha256::digest(token.as_bytes());
|
||||
digest.iter().map(|byte| format!("{byte:02x}")).collect()
|
||||
@@ -530,11 +609,17 @@ fn normalize_editor_config_snapshot(
|
||||
config: &mut BarkBattleEditorConfigSnapshot,
|
||||
) -> Result<(), String> {
|
||||
config.title = normalize_title(Some(&config.title))?;
|
||||
config.theme_preset = normalize_required_preset(&config.theme_preset, "theme_preset")?;
|
||||
config.player_dog_skin_preset =
|
||||
normalize_required_preset(&config.player_dog_skin_preset, "player_dog_skin_preset")?;
|
||||
config.opponent_dog_skin_preset =
|
||||
normalize_required_preset(&config.opponent_dog_skin_preset, "opponent_dog_skin_preset")?;
|
||||
config.theme_description =
|
||||
normalize_required_description(&config.theme_description, "theme_description")?;
|
||||
config.player_image_description = normalize_required_description(
|
||||
&config.player_image_description,
|
||||
"player_image_description",
|
||||
)?;
|
||||
config.opponent_image_description = normalize_required_description(
|
||||
&config.opponent_image_description,
|
||||
"opponent_image_description",
|
||||
)?;
|
||||
config.onomatopoeia = normalize_onomatopoeia(std::mem::take(&mut config.onomatopoeia));
|
||||
config.player_character_image_src = normalize_optional_asset_source(
|
||||
config.player_character_image_src.as_deref(),
|
||||
"player_character_image_src",
|
||||
@@ -547,8 +632,6 @@ fn normalize_editor_config_snapshot(
|
||||
config.ui_background_image_src.as_deref(),
|
||||
"ui_background_image_src",
|
||||
)?;
|
||||
config.bark_sound_src =
|
||||
normalize_optional_asset_source(config.bark_sound_src.as_deref(), "bark_sound_src")?;
|
||||
config.difficulty_preset = normalize_difficulty(Some(&config.difficulty_preset))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -568,12 +651,24 @@ fn normalize_optional_text(value: Option<&str>) -> String {
|
||||
value.unwrap_or_default().trim().chars().take(120).collect()
|
||||
}
|
||||
|
||||
fn normalize_required_preset(value: &str, field_name: &str) -> Result<String, String> {
|
||||
let preset = value.trim();
|
||||
if preset.is_empty() {
|
||||
fn normalize_required_description(value: &str, field_name: &str) -> Result<String, String> {
|
||||
let description = value.trim();
|
||||
if description.is_empty() {
|
||||
return Err(format!("bark_battle {field_name} 不能为空"));
|
||||
}
|
||||
Ok(preset.to_string())
|
||||
if description.chars().count() > 240 {
|
||||
return Err(format!("bark_battle {field_name} 不能超过 240 个字符"));
|
||||
}
|
||||
Ok(description.to_string())
|
||||
}
|
||||
|
||||
fn normalize_onomatopoeia(words: Vec<String>) -> Vec<String> {
|
||||
words
|
||||
.into_iter()
|
||||
.map(|word| word.trim().chars().take(12).collect::<String>())
|
||||
.filter(|word| !word.is_empty())
|
||||
.take(24)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn normalize_optional_asset_source(
|
||||
@@ -674,7 +769,6 @@ fn run_snapshot(row: &BarkBattleRuntimeRunRow) -> BarkBattleRunSnapshot {
|
||||
config_version: row.config_version,
|
||||
ruleset_version: row.ruleset_version.clone(),
|
||||
difficulty_preset: row.difficulty_preset.clone(),
|
||||
leaderboard_enabled: row.leaderboard_enabled,
|
||||
status: row.status.clone(),
|
||||
client_started_at_micros: row.client_started_at_micros,
|
||||
server_started_at_micros: row.server_started_at.to_micros_since_unix_epoch(),
|
||||
@@ -905,7 +999,6 @@ mod tests {
|
||||
config_version: 1,
|
||||
ruleset_version: BARK_BATTLE_DEFAULT_RULESET_VERSION.to_string(),
|
||||
difficulty_preset: BARK_BATTLE_DIFFICULTY_NORMAL.to_string(),
|
||||
leaderboard_enabled: true,
|
||||
config_json: "{}".to_string(),
|
||||
updated_at_micros: 1_700_000,
|
||||
};
|
||||
@@ -919,7 +1012,6 @@ mod tests {
|
||||
config_version: input.config_version,
|
||||
ruleset_version: input.ruleset_version.clone(),
|
||||
difficulty_preset: input.difficulty_preset.clone(),
|
||||
leaderboard_enabled: input.leaderboard_enabled,
|
||||
config_json: input.config_json.clone(),
|
||||
editor_state_json: "{}".to_string(),
|
||||
created_at_micros: 1_700_000,
|
||||
@@ -945,4 +1037,84 @@ mod tests {
|
||||
assert!(normalize_title(Some(" 标题 ")).is_ok());
|
||||
assert!(normalize_title(Some(" ")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn published_snapshot_is_normalized_as_runtime_config() {
|
||||
let mut editor_config = parse_editor_config(
|
||||
&serde_json::json!({
|
||||
"title": " 汪汪测试杯 ",
|
||||
"description": "",
|
||||
"themeDescription": " 阳光草坪 ",
|
||||
"playerImageDescription": " 主角柴犬 ",
|
||||
"opponentImageDescription": " 对手哈士奇 ",
|
||||
"onomatopoeia": [" 轰汪! ", "冲啊冲啊冲啊冲啊冲啊!", ""],
|
||||
"playerCharacterImageSrc": "/generated-bark-battle-assets/player.png",
|
||||
"opponentCharacterImageSrc": "/generated-bark-battle-assets/opponent.png",
|
||||
"uiBackgroundImageSrc": "/generated-bark-battle-assets/ui.png",
|
||||
"difficultyPreset": "normal"
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("published snapshot should parse");
|
||||
|
||||
normalize_editor_config_snapshot(&mut editor_config)
|
||||
.expect("published snapshot should normalize");
|
||||
let config_json = to_json_string(&editor_config);
|
||||
|
||||
assert!(config_json.contains("/generated-bark-battle-assets/player.png"));
|
||||
assert!(config_json.contains("/generated-bark-battle-assets/opponent.png"));
|
||||
assert!(config_json.contains("/generated-bark-battle-assets/ui.png"));
|
||||
assert!(config_json.contains("阳光草坪"));
|
||||
assert!(config_json.contains("轰汪!"));
|
||||
assert!(config_json.contains("冲啊冲啊冲啊冲啊"));
|
||||
assert!(!config_json.contains("冲啊冲啊冲啊冲啊冲啊!"));
|
||||
assert!(!config_json.contains("\"title\":\" 汪汪测试杯 \""));
|
||||
assert!(!config_json.contains("themePreset"));
|
||||
assert!(!config_json.contains("playerDogSkinPreset"));
|
||||
assert!(!config_json.contains("opponentDogSkinPreset"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bark_battle_gallery_view_row_exposes_custom_onomatopoeia() {
|
||||
let mut editor_config = parse_editor_config(
|
||||
&serde_json::json!({
|
||||
"title": "声浪公开赛",
|
||||
"description": "画廊映射测试",
|
||||
"themeDescription": "霓虹竞技场",
|
||||
"playerImageDescription": "星际猫骑士",
|
||||
"opponentImageDescription": "机器人拳手",
|
||||
"onomatopoeia": [" 轰! ", "炸场!", ""],
|
||||
"difficultyPreset": "normal"
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("gallery config should parse");
|
||||
|
||||
normalize_editor_config_snapshot(&mut editor_config)
|
||||
.expect("gallery config should normalize");
|
||||
|
||||
let row = BarkBattleGalleryViewRow {
|
||||
work_id: "BB-33333333".to_string(),
|
||||
owner_user_id: "user-3".to_string(),
|
||||
source_draft_id: Some("bark-battle-draft-3".to_string()),
|
||||
config_version: 1,
|
||||
ruleset_version: BARK_BATTLE_DEFAULT_RULESET_VERSION.to_string(),
|
||||
difficulty_preset: editor_config.difficulty_preset.clone(),
|
||||
title: editor_config.title,
|
||||
description: editor_config.description,
|
||||
theme_description: editor_config.theme_description,
|
||||
player_image_description: editor_config.player_image_description,
|
||||
opponent_image_description: editor_config.opponent_image_description,
|
||||
onomatopoeia: editor_config.onomatopoeia,
|
||||
player_character_image_src: editor_config.player_character_image_src,
|
||||
opponent_character_image_src: editor_config.opponent_character_image_src,
|
||||
ui_background_image_src: editor_config.ui_background_image_src,
|
||||
play_count: 8,
|
||||
finish_count: 5,
|
||||
updated_at_micros: 1_713_686_401_234_567,
|
||||
published_at_micros: 1_713_686_401_234_000,
|
||||
};
|
||||
|
||||
assert_eq!(row.onomatopoeia, vec!["轰!".to_string(), "炸场!".to_string()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,10 @@ pub struct BarkBattleDraftCreateInput {
|
||||
pub work_id: String,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub theme_preset: String,
|
||||
pub player_dog_skin_preset: String,
|
||||
pub opponent_dog_skin_preset: String,
|
||||
pub theme_description: String,
|
||||
pub player_image_description: String,
|
||||
pub opponent_image_description: String,
|
||||
pub difficulty_preset: Option<String>,
|
||||
pub leaderboard_enabled: Option<bool>,
|
||||
pub editor_state_json: Option<String>,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
@@ -41,7 +40,6 @@ pub struct BarkBattleDraftConfigUpsertInput {
|
||||
pub config_version: u64,
|
||||
pub ruleset_version: String,
|
||||
pub difficulty_preset: String,
|
||||
pub leaderboard_enabled: bool,
|
||||
pub config_json: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
@@ -116,19 +114,18 @@ pub struct BarkBattleProcedureResult {
|
||||
pub struct BarkBattleEditorConfigSnapshot {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub theme_preset: String,
|
||||
pub player_dog_skin_preset: String,
|
||||
pub opponent_dog_skin_preset: String,
|
||||
pub theme_description: String,
|
||||
pub player_image_description: String,
|
||||
pub opponent_image_description: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub onomatopoeia: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub player_character_image_src: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub opponent_character_image_src: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ui_background_image_src: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub bark_sound_src: Option<String>,
|
||||
pub difficulty_preset: String,
|
||||
pub leaderboard_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, SpacetimeType)]
|
||||
@@ -140,7 +137,6 @@ pub struct BarkBattleDraftConfigSnapshot {
|
||||
pub config_version: u64,
|
||||
pub ruleset_version: String,
|
||||
pub difficulty_preset: String,
|
||||
pub leaderboard_enabled: bool,
|
||||
pub config_json: String,
|
||||
pub editor_state_json: String,
|
||||
pub created_at_micros: i64,
|
||||
@@ -156,7 +152,6 @@ pub struct BarkBattleRuntimeConfigSnapshot {
|
||||
pub config_version: u64,
|
||||
pub ruleset_version: String,
|
||||
pub difficulty_preset: String,
|
||||
pub leaderboard_enabled: bool,
|
||||
pub config_json: String,
|
||||
pub published_snapshot_json: String,
|
||||
pub published_at_micros: i64,
|
||||
@@ -172,7 +167,6 @@ pub struct BarkBattleRunSnapshot {
|
||||
pub config_version: u64,
|
||||
pub ruleset_version: String,
|
||||
pub difficulty_preset: String,
|
||||
pub leaderboard_enabled: bool,
|
||||
pub status: String,
|
||||
pub client_started_at_micros: i64,
|
||||
pub server_started_at_micros: i64,
|
||||
@@ -185,3 +179,31 @@ pub struct BarkBattleRunSnapshot {
|
||||
pub leaderboard_score: Option<u64>,
|
||||
pub score_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Bark Battle 公开广场只读投影行。
|
||||
///
|
||||
/// 该结构只暴露 v1 公共卡片需要的配置和基础统计,不把内部排行榜开关或旧皮肤 /
|
||||
/// 音效预设重新带回公开语义。
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, SpacetimeType)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BarkBattleGalleryViewRow {
|
||||
pub work_id: String,
|
||||
pub owner_user_id: String,
|
||||
pub source_draft_id: Option<String>,
|
||||
pub config_version: u64,
|
||||
pub ruleset_version: String,
|
||||
pub difficulty_preset: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub theme_description: String,
|
||||
pub player_image_description: String,
|
||||
pub opponent_image_description: String,
|
||||
pub onomatopoeia: Vec<String>,
|
||||
pub player_character_image_src: Option<String>,
|
||||
pub opponent_character_image_src: Option<String>,
|
||||
pub ui_background_image_src: Option<String>,
|
||||
pub play_count: u64,
|
||||
pub finish_count: u64,
|
||||
pub updated_at_micros: i64,
|
||||
pub published_at_micros: i64,
|
||||
}
|
||||
|
||||
@@ -180,17 +180,7 @@ fn seed_creation_entry_config_if_missing(ctx: &ReducerContext) {
|
||||
}
|
||||
|
||||
migrate_visual_novel_entry_from_old_visible_default(ctx, now);
|
||||
migrate_coming_soon_entry_from_old_open_default(
|
||||
ctx,
|
||||
now,
|
||||
ComingSoonEntryDefault {
|
||||
id: "bark-battle",
|
||||
title: "汪汪声浪",
|
||||
subtitle: "声控对战挑战",
|
||||
image_src: "/creation-type-references/creative-agent.webp",
|
||||
sort_order: 85,
|
||||
},
|
||||
);
|
||||
migrate_bark_battle_entry_to_open_default(ctx, now);
|
||||
migrate_coming_soon_entry_from_old_open_default(
|
||||
ctx,
|
||||
now,
|
||||
@@ -204,6 +194,36 @@ fn seed_creation_entry_config_if_missing(ctx: &ReducerContext) {
|
||||
);
|
||||
}
|
||||
|
||||
fn migrate_bark_battle_entry_to_open_default(ctx: &ReducerContext, now: Timestamp) {
|
||||
let id = "bark-battle".to_string();
|
||||
let Some(row) = ctx.db.creation_entry_type_config().id().find(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// 中文注释:只纠偏系统默认汪汪声浪入口,不覆盖后台手动改过标题、排序或可见性的配置。
|
||||
let still_system_default = row.title == "汪汪声浪"
|
||||
&& row.subtitle == "声控对战挑战"
|
||||
&& row.visible
|
||||
&& row.sort_order == 85
|
||||
&& (row.image_src == "/creation-type-references/creative-agent.webp"
|
||||
|| row.image_src == "/creation-type-references/bark-battle.webp")
|
||||
&& ((row.badge == "敬请期待" && !row.open) || (row.badge == "可创建" && row.open));
|
||||
if !still_system_default {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.db
|
||||
.creation_entry_type_config()
|
||||
.id()
|
||||
.update(CreationEntryTypeConfig {
|
||||
badge: "可创建".to_string(),
|
||||
image_src: "/creation-type-references/bark-battle.webp".to_string(),
|
||||
open: true,
|
||||
updated_at: now,
|
||||
..row
|
||||
});
|
||||
}
|
||||
|
||||
fn migrate_visual_novel_entry_from_old_visible_default(ctx: &ReducerContext, now: Timestamp) {
|
||||
let id = "visual-novel".to_string();
|
||||
let Some(row) = ctx.db.creation_entry_type_config().id().find(&id) else {
|
||||
|
||||
Reference in New Issue
Block a user