1
This commit is contained in:
@@ -325,7 +325,7 @@ pub struct CustomWorldProfile {
|
||||
owner_user_id: String,
|
||||
// 作品公开编号是稳定分享键,第一次发布时分配,后续重复发布沿用。
|
||||
public_work_code: Option<String>,
|
||||
// 作者公开叙世号在发布时固化到作品真相,供广场读模型与搜索结果直接展示。
|
||||
// 作者公开陶泥号在发布时固化到作品真相,供广场读模型与搜索结果直接展示。
|
||||
author_public_user_code: Option<String>,
|
||||
source_agent_session_id: Option<String>,
|
||||
publication_status: CustomWorldPublicationStatus,
|
||||
@@ -337,16 +337,19 @@ pub struct CustomWorldProfile {
|
||||
profile_payload_json: String,
|
||||
playable_npc_count: u32,
|
||||
landmark_count: u32,
|
||||
// 公开消费计数随 profile 真相持久化,发布、编辑和取消发布都不能重置。
|
||||
play_count: u32,
|
||||
remix_count: u32,
|
||||
like_count: u32,
|
||||
author_display_name: String,
|
||||
published_at: Option<Timestamp>,
|
||||
// 软删除后保留 profile 真相,供审计与幂等删除使用。
|
||||
deleted_at: Option<Timestamp>,
|
||||
created_at: Timestamp,
|
||||
updated_at: Timestamp,
|
||||
// 公开消费计数随 profile 真相持久化,发布、编辑和取消发布都不能重置。
|
||||
#[default(0)]
|
||||
play_count: u32,
|
||||
#[default(0)]
|
||||
remix_count: u32,
|
||||
#[default(0)]
|
||||
like_count: u32,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
@@ -488,12 +491,15 @@ pub struct CustomWorldGalleryEntry {
|
||||
theme_mode: CustomWorldThemeMode,
|
||||
playable_npc_count: u32,
|
||||
landmark_count: u32,
|
||||
// 画廊读模型直接同步互动计数,避免前端临时把评分或游玩数改名成点赞。
|
||||
play_count: u32,
|
||||
remix_count: u32,
|
||||
like_count: u32,
|
||||
published_at: Timestamp,
|
||||
updated_at: Timestamp,
|
||||
// 画廊读模型直接同步互动计数,避免前端临时把评分或游玩数改名成点赞。
|
||||
#[default(0)]
|
||||
play_count: u32,
|
||||
#[default(0)]
|
||||
remix_count: u32,
|
||||
#[default(0)]
|
||||
like_count: u32,
|
||||
}
|
||||
|
||||
// 成长状态默认按 user_id 单行持久化;若尚未存在记录则返回 Lv.1 / 0 XP 的兼容初始值。
|
||||
@@ -2839,9 +2845,10 @@ fn list_custom_world_profile_snapshots(
|
||||
let mut entries = ctx
|
||||
.db
|
||||
.custom_world_profile()
|
||||
.iter()
|
||||
.filter(|row| row.owner_user_id == input.owner_user_id && row.deleted_at.is_none())
|
||||
.map(|row| build_custom_world_profile_snapshot(&row))
|
||||
.by_custom_world_profile_owner_user_id()
|
||||
.filter(&input.owner_user_id)
|
||||
.filter(|row| row.deleted_at.is_none())
|
||||
.map(|row| build_custom_world_profile_list_snapshot(&row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
entries.sort_by(|left, right| right.updated_at_micros.cmp(&left.updated_at_micros));
|
||||
@@ -2849,6 +2856,86 @@ fn list_custom_world_profile_snapshots(
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn build_custom_world_profile_list_snapshot(row: &CustomWorldProfile) -> CustomWorldProfileSnapshot {
|
||||
let mut snapshot = build_custom_world_profile_snapshot(row);
|
||||
snapshot.profile_payload_json = build_custom_world_profile_list_payload_json(row);
|
||||
snapshot
|
||||
}
|
||||
|
||||
fn build_custom_world_profile_list_payload_json(row: &CustomWorldProfile) -> String {
|
||||
let source_profile = serde_json::from_str::<JsonValue>(&row.profile_payload_json).ok();
|
||||
let source_object = source_profile.as_ref().and_then(JsonValue::as_object);
|
||||
let empty_roles = JsonValue::Array(Vec::new());
|
||||
let empty_landmarks = JsonValue::Array(Vec::new());
|
||||
|
||||
// 中文注释:首屏作品列表只需要卡片摘要,不能继续把完整 profile 大 JSON 随列表搬回 Axum。
|
||||
let payload = json!({
|
||||
"id": row.profile_id,
|
||||
"name": row.world_name,
|
||||
"subtitle": row.subtitle,
|
||||
"summary": row.summary_text,
|
||||
"tone": source_object
|
||||
.and_then(|object| object.get("tone"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.unwrap_or_default(),
|
||||
"playerGoal": source_object
|
||||
.and_then(|object| object.get("playerGoal"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.unwrap_or_default(),
|
||||
"settingText": source_object
|
||||
.and_then(|object| object.get("settingText"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.unwrap_or_default(),
|
||||
"themeMode": row.theme_mode.as_str(),
|
||||
"templateWorldType": source_object
|
||||
.and_then(|object| object.get("templateWorldType"))
|
||||
.and_then(JsonValue::as_str)
|
||||
.unwrap_or("WUXIA"),
|
||||
"compatibilityTemplateWorldType": source_object
|
||||
.and_then(|object| object.get("compatibilityTemplateWorldType"))
|
||||
.cloned()
|
||||
.unwrap_or(JsonValue::Null),
|
||||
"cover": row.cover_image_src.as_ref().map(|image_src| json!({
|
||||
"sourceType": "generated",
|
||||
"imageSrc": image_src,
|
||||
})),
|
||||
"majorFactions": source_object
|
||||
.and_then(|object| object.get("majorFactions"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| JsonValue::Array(Vec::new())),
|
||||
"coreConflicts": source_object
|
||||
.and_then(|object| object.get("coreConflicts"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| JsonValue::Array(Vec::new())),
|
||||
"playableNpcs": source_object
|
||||
.and_then(|object| object.get("playableNpcs"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| empty_roles.clone()),
|
||||
"storyNpcs": source_object
|
||||
.and_then(|object| object.get("storyNpcs"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| JsonValue::Array(Vec::new())),
|
||||
"items": source_object
|
||||
.and_then(|object| object.get("items"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| JsonValue::Array(Vec::new())),
|
||||
"camp": source_object
|
||||
.and_then(|object| object.get("camp"))
|
||||
.cloned()
|
||||
.unwrap_or(JsonValue::Null),
|
||||
"landmarks": source_object
|
||||
.and_then(|object| object.get("landmarks"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| empty_landmarks.clone()),
|
||||
"ownedSettingLayers": source_object
|
||||
.and_then(|object| object.get("ownedSettingLayers"))
|
||||
.cloned()
|
||||
.unwrap_or(JsonValue::Null),
|
||||
});
|
||||
|
||||
serde_json::to_string(&payload).unwrap_or_else(|_| "{}".to_string())
|
||||
}
|
||||
|
||||
fn list_custom_world_gallery_snapshots(
|
||||
ctx: &ReducerContext,
|
||||
) -> Result<Vec<CustomWorldGalleryEntrySnapshot>, String> {
|
||||
@@ -2858,7 +2945,7 @@ fn list_custom_world_gallery_snapshots(
|
||||
.db
|
||||
.custom_world_gallery_entry()
|
||||
.iter()
|
||||
.map(|row| build_custom_world_gallery_entry_snapshot(&row))
|
||||
.map(|row| build_custom_world_gallery_entry_snapshot(ctx, &row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
entries.sort_by(|left, right| {
|
||||
@@ -2905,7 +2992,7 @@ fn get_custom_world_library_detail_record(
|
||||
profile.as_ref().map(build_custom_world_profile_snapshot),
|
||||
gallery_entry
|
||||
.as_ref()
|
||||
.map(build_custom_world_gallery_entry_snapshot),
|
||||
.map(|row| build_custom_world_gallery_entry_snapshot(ctx, row)),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -2943,7 +3030,7 @@ fn get_custom_world_gallery_detail_record(
|
||||
profile.as_ref().map(build_custom_world_profile_snapshot),
|
||||
gallery_entry
|
||||
.as_ref()
|
||||
.map(build_custom_world_gallery_entry_snapshot),
|
||||
.map(|row| build_custom_world_gallery_entry_snapshot(ctx, row)),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -2985,7 +3072,7 @@ fn get_custom_world_gallery_detail_record_by_code(
|
||||
profile.as_ref().map(build_custom_world_profile_snapshot),
|
||||
gallery_entry
|
||||
.as_ref()
|
||||
.map(build_custom_world_gallery_entry_snapshot),
|
||||
.map(|row| build_custom_world_gallery_entry_snapshot(ctx, row)),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -3123,6 +3210,15 @@ fn record_custom_world_profile_play_record(
|
||||
})
|
||||
.ok_or_else(|| "custom_world 已发布作品不存在,无法记录游玩".to_string())?;
|
||||
let played_at = Timestamp::from_micros_since_unix_epoch(input.played_at_micros);
|
||||
record_public_work_play(
|
||||
ctx,
|
||||
crate::runtime::PublicWorkPlayRecordInput {
|
||||
source_type: "custom-world".to_string(),
|
||||
owner_user_id: existing.owner_user_id.clone(),
|
||||
profile_id: existing.profile_id.clone(),
|
||||
played_at_micros: input.played_at_micros,
|
||||
},
|
||||
)?;
|
||||
// 游玩计数是公开广场消费数据,只增加计数并保持作品内容不变。
|
||||
let next_row = CustomWorldProfile {
|
||||
profile_id: existing.profile_id.clone(),
|
||||
@@ -3790,7 +3886,7 @@ fn execute_publish_world_action(
|
||||
let author_public_user_code = read_optional_text_field(payload, &["authorPublicUserCode"])
|
||||
.unwrap_or_else(|| build_public_user_code_from_owner_user_id(&session.owner_user_id));
|
||||
let author_display_name = read_optional_text_field(payload, &["authorDisplayName"])
|
||||
.unwrap_or_else(|| "创作者".to_string());
|
||||
.unwrap_or_else(|| "陶泥主".to_string());
|
||||
let publish_result = publish_custom_world_world_record(
|
||||
ctx,
|
||||
CustomWorldPublishWorldInput {
|
||||
@@ -5299,7 +5395,7 @@ fn sync_custom_world_gallery_entry_from_profile(
|
||||
|
||||
let inserted = ctx.db.custom_world_gallery_entry().insert(row);
|
||||
|
||||
Ok(build_custom_world_gallery_entry_snapshot(&inserted))
|
||||
Ok(build_custom_world_gallery_entry_snapshot(ctx, &inserted))
|
||||
}
|
||||
|
||||
fn sync_missing_custom_world_gallery_entries(ctx: &ReducerContext) -> Result<(), String> {
|
||||
@@ -5570,6 +5666,7 @@ fn build_custom_world_draft_card_snapshot(
|
||||
}
|
||||
|
||||
fn build_custom_world_gallery_entry_snapshot(
|
||||
ctx: &ReducerContext,
|
||||
row: &CustomWorldGalleryEntry,
|
||||
) -> CustomWorldGalleryEntrySnapshot {
|
||||
CustomWorldGalleryEntrySnapshot {
|
||||
@@ -5588,6 +5685,12 @@ fn build_custom_world_gallery_entry_snapshot(
|
||||
play_count: row.play_count,
|
||||
remix_count: row.remix_count,
|
||||
like_count: row.like_count,
|
||||
recent_play_count_7d: count_recent_public_work_plays(
|
||||
ctx,
|
||||
"custom-world",
|
||||
&row.profile_id,
|
||||
ctx.timestamp.to_micros_since_unix_epoch(),
|
||||
),
|
||||
published_at_micros: row.published_at.to_micros_since_unix_epoch(),
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user