feat(api-server): cache puzzle gallery card view
This commit is contained in:
@@ -114,10 +114,10 @@ pub struct PuzzleWorkProfileRow {
|
||||
point_incentive_claimed_points: u64,
|
||||
}
|
||||
|
||||
/// 拼图广场公开列表投影。
|
||||
/// 拼图广场公开详情兼容投影。
|
||||
///
|
||||
/// `puzzle_work_profile` 是私有真相表,HTTP gallery 只订阅这个 view,
|
||||
/// 避免每次请求回到 procedure 重新扫表、组装列表和跨层 JSON 往返。
|
||||
/// 该 view 返回完整 `PuzzleWorkProfile`,包含 levels / anchor_pack 等详情级字段。
|
||||
/// 公开列表主路径应订阅更轻量的 `puzzle_gallery_card_view`。
|
||||
#[spacetimedb::view(accessor = puzzle_gallery_view, public)]
|
||||
pub fn puzzle_gallery_view(ctx: &AnonymousViewContext) -> Vec<PuzzleWorkProfile> {
|
||||
let mut items = ctx
|
||||
@@ -125,11 +125,40 @@ pub fn puzzle_gallery_view(ctx: &AnonymousViewContext) -> Vec<PuzzleWorkProfile>
|
||||
.puzzle_work_profile()
|
||||
.by_puzzle_work_publication_status()
|
||||
.filter(PuzzlePublicationStatus::Published)
|
||||
.filter_map(|row| match build_puzzle_work_profile_from_row_without_recent_count(&row) {
|
||||
Ok(profile) => Some(profile),
|
||||
.filter_map(
|
||||
|row| match build_puzzle_work_profile_from_row_without_recent_count(&row) {
|
||||
Ok(profile) => Some(profile),
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
"拼图广场 view 跳过损坏的作品投影 profile_id={}: {}",
|
||||
row.profile_id,
|
||||
error
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| right.updated_at_micros.cmp(&left.updated_at_micros));
|
||||
items
|
||||
}
|
||||
|
||||
/// 拼图广场公开列表卡片投影。
|
||||
///
|
||||
/// 该 view 只暴露前端列表首屏需要的公开卡片字段,不携带 levels / anchor_pack
|
||||
/// 等详情级载荷,供 api-server 热点缓存订阅和组装列表窗口。
|
||||
#[spacetimedb::view(accessor = puzzle_gallery_card_view, public)]
|
||||
pub fn puzzle_gallery_card_view(ctx: &AnonymousViewContext) -> Vec<PuzzleGalleryCardViewRow> {
|
||||
let mut items = ctx
|
||||
.db
|
||||
.puzzle_work_profile()
|
||||
.by_puzzle_work_publication_status()
|
||||
.filter(PuzzlePublicationStatus::Published)
|
||||
.filter_map(|row| match build_puzzle_gallery_card_view_row(&row) {
|
||||
Ok(item) => Some(item),
|
||||
Err(error) => {
|
||||
log::warn!(
|
||||
"拼图广场 view 跳过损坏的作品投影 profile_id={}: {}",
|
||||
"拼图广场卡片 view 跳过损坏的作品投影 profile_id={}: {}",
|
||||
row.profile_id,
|
||||
error
|
||||
);
|
||||
@@ -137,10 +166,41 @@ pub fn puzzle_gallery_view(ctx: &AnonymousViewContext) -> Vec<PuzzleWorkProfile>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| right.updated_at_micros.cmp(&left.updated_at_micros));
|
||||
items.sort_by(|left, right| {
|
||||
right
|
||||
.updated_at_micros
|
||||
.cmp(&left.updated_at_micros)
|
||||
.then_with(|| left.profile_id.cmp(&right.profile_id))
|
||||
});
|
||||
items
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub struct PuzzleGalleryCardViewRow {
|
||||
pub work_id: String,
|
||||
pub profile_id: String,
|
||||
pub owner_user_id: String,
|
||||
pub source_session_id: Option<String>,
|
||||
pub author_display_name: String,
|
||||
pub work_title: String,
|
||||
pub work_description: String,
|
||||
pub level_name: String,
|
||||
pub summary: String,
|
||||
pub theme_tags: Vec<String>,
|
||||
pub cover_image_src: Option<String>,
|
||||
pub cover_asset_id: Option<String>,
|
||||
pub publication_status: PuzzlePublicationStatus,
|
||||
pub updated_at_micros: i64,
|
||||
pub published_at_micros: Option<i64>,
|
||||
pub play_count: u32,
|
||||
pub remix_count: u32,
|
||||
pub like_count: u32,
|
||||
pub point_incentive_total_half_points: u64,
|
||||
pub point_incentive_claimed_points: u64,
|
||||
pub publish_ready: bool,
|
||||
pub generation_status: Option<String>,
|
||||
}
|
||||
|
||||
/// 拼图创作事件类型。
|
||||
///
|
||||
/// 事件表只广播跨层订阅需要的轻量事实,作品真相仍以
|
||||
@@ -2444,6 +2504,68 @@ fn build_puzzle_work_profile_from_row_without_recent_count(
|
||||
})
|
||||
}
|
||||
|
||||
fn build_puzzle_gallery_card_view_row(
|
||||
row: &PuzzleWorkProfileRow,
|
||||
) -> Result<PuzzleGalleryCardViewRow, String> {
|
||||
let levels = build_profile_levels_from_row(row)?;
|
||||
Ok(PuzzleGalleryCardViewRow {
|
||||
work_id: row.work_id.clone(),
|
||||
profile_id: row.profile_id.clone(),
|
||||
owner_user_id: row.owner_user_id.clone(),
|
||||
source_session_id: row.source_session_id.clone(),
|
||||
author_display_name: row.author_display_name.clone(),
|
||||
work_title: if row.work_title.trim().is_empty() {
|
||||
row.level_name.clone()
|
||||
} else {
|
||||
row.work_title.clone()
|
||||
},
|
||||
work_description: if row.work_description.trim().is_empty() {
|
||||
row.summary.clone()
|
||||
} else {
|
||||
row.work_description.clone()
|
||||
},
|
||||
level_name: row.level_name.clone(),
|
||||
summary: row.summary.clone(),
|
||||
theme_tags: deserialize_theme_tags(&row.theme_tags_json)?,
|
||||
cover_image_src: row.cover_image_src.clone(),
|
||||
cover_asset_id: row.cover_asset_id.clone(),
|
||||
publication_status: row.publication_status,
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
published_at_micros: row
|
||||
.published_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
play_count: row.play_count,
|
||||
remix_count: row.remix_count,
|
||||
like_count: row.like_count,
|
||||
point_incentive_total_half_points: row.point_incentive_total_half_points,
|
||||
point_incentive_claimed_points: row.point_incentive_claimed_points,
|
||||
publish_ready: row.publish_ready,
|
||||
generation_status: resolve_puzzle_gallery_generation_status(&levels),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_puzzle_gallery_generation_status(
|
||||
levels: &[module_puzzle::PuzzleDraftLevel],
|
||||
) -> Option<String> {
|
||||
levels
|
||||
.iter()
|
||||
.map(|level| level.generation_status.trim())
|
||||
.find(|status| *status == "generating")
|
||||
.or_else(|| {
|
||||
levels
|
||||
.iter()
|
||||
.map(|level| level.generation_status.trim())
|
||||
.find(|status| *status == "ready")
|
||||
})
|
||||
.or_else(|| {
|
||||
levels
|
||||
.iter()
|
||||
.map(|level| level.generation_status.trim())
|
||||
.find(|status| !status.is_empty())
|
||||
})
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
fn build_profile_levels_from_row(
|
||||
row: &PuzzleWorkProfileRow,
|
||||
) -> Result<Vec<module_puzzle::PuzzleDraftLevel>, String> {
|
||||
|
||||
Reference in New Issue
Block a user