feat(api-server): cache puzzle gallery card view

This commit is contained in:
kdletters
2026-05-17 05:50:33 +08:00
parent 02271e6c73
commit 73f937d78a
17 changed files with 771 additions and 44 deletions

View File

@@ -44,7 +44,7 @@ pub use mapper::{
PuzzleAgentSessionRecord, PuzzleAgentSuggestedActionRecord, PuzzleAnchorItemRecord,
PuzzleAnchorPackRecord, PuzzleAudioAssetRecord, PuzzleBoardRecord, PuzzleCellPositionRecord,
PuzzleCreatorIntentRecord, PuzzleDraftLevelRecord, PuzzleFormDraftRecord,
PuzzleFormDraftSaveRecordInput, PuzzleGeneratedImageCandidateRecord,
PuzzleFormDraftSaveRecordInput, PuzzleGalleryCardRecord, PuzzleGeneratedImageCandidateRecord,
PuzzleGeneratedImagesSaveRecordInput, PuzzleLeaderboardEntryRecord,
PuzzleLeaderboardSubmitRecordInput, PuzzleMergedGroupRecord, PuzzlePieceStateRecord,
PuzzlePublishRecordInput, PuzzleRecommendedNextWorkRecord, PuzzleResultDraftRecord,
@@ -540,7 +540,7 @@ impl SpacetimeClient {
) -> Result<Vec<SubscriptionHandle>, SpacetimeClientError> {
let mut subscriptions = Vec::new();
for query in [
"SELECT * FROM puzzle_gallery_view",
"SELECT * FROM puzzle_gallery_card_view",
"SELECT * FROM custom_world_gallery_entry",
"SELECT * FROM match_3_d_gallery_view",
"SELECT * FROM square_hole_gallery_view",

View File

@@ -4027,8 +4027,36 @@ pub(crate) fn map_puzzle_work_profile(snapshot: PuzzleWorkProfile) -> PuzzleWork
}
}
pub(crate) fn map_puzzle_work_profile_row(snapshot: PuzzleWorkProfile) -> PuzzleWorkProfileRecord {
map_puzzle_work_profile(snapshot)
pub(crate) fn map_puzzle_gallery_card_view_row(
snapshot: PuzzleGalleryCardViewRow,
recent_play_count_7d: u32,
) -> PuzzleGalleryCardRecord {
PuzzleGalleryCardRecord {
work_id: snapshot.work_id,
profile_id: snapshot.profile_id,
owner_user_id: snapshot.owner_user_id,
source_session_id: snapshot.source_session_id,
author_display_name: snapshot.author_display_name,
work_title: snapshot.work_title,
work_description: snapshot.work_description,
level_name: snapshot.level_name,
summary: snapshot.summary,
theme_tags: snapshot.theme_tags,
cover_image_src: snapshot.cover_image_src,
cover_asset_id: snapshot.cover_asset_id,
publication_status: format_puzzle_publication_status(snapshot.publication_status)
.to_string(),
updated_at: format_timestamp_micros(snapshot.updated_at_micros),
published_at: snapshot.published_at_micros.map(format_timestamp_micros),
play_count: snapshot.play_count,
remix_count: snapshot.remix_count,
like_count: snapshot.like_count,
recent_play_count_7d,
point_incentive_total_half_points: snapshot.point_incentive_total_half_points,
point_incentive_claimed_points: snapshot.point_incentive_claimed_points,
publish_ready: snapshot.publish_ready,
generation_status: snapshot.generation_status,
}
}
pub(crate) fn map_puzzle_run_snapshot(snapshot: PuzzleRunSnapshot) -> PuzzleRunRecord {
@@ -7412,6 +7440,33 @@ pub struct PuzzleWorkProfileRecord {
pub levels: Vec<PuzzleDraftLevelRecord>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PuzzleGalleryCardRecord {
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: String,
pub updated_at: String,
pub published_at: Option<String>,
pub play_count: u32,
pub remix_count: u32,
pub like_count: u32,
pub recent_play_count_7d: u32,
pub point_incentive_total_half_points: u64,
pub point_incentive_claimed_points: u64,
pub publish_ready: bool,
pub generation_status: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PuzzleWorkPointIncentiveClaimRecordInput {
pub profile_id: String,

View File

@@ -542,6 +542,8 @@ pub mod puzzle_event_table;
pub mod puzzle_event_type;
pub mod puzzle_form_draft_save_input_type;
pub mod puzzle_form_draft_type;
pub mod puzzle_gallery_card_view_row_type;
pub mod puzzle_gallery_card_view_table;
pub mod puzzle_gallery_view_table;
pub mod puzzle_generated_image_candidate_type;
pub mod puzzle_generated_images_save_input_type;
@@ -1464,6 +1466,8 @@ pub use puzzle_event_table::*;
pub use puzzle_event_type::PuzzleEvent;
pub use puzzle_form_draft_save_input_type::PuzzleFormDraftSaveInput;
pub use puzzle_form_draft_type::PuzzleFormDraft;
pub use puzzle_gallery_card_view_row_type::PuzzleGalleryCardViewRow;
pub use puzzle_gallery_card_view_table::*;
pub use puzzle_gallery_view_table::*;
pub use puzzle_generated_image_candidate_type::PuzzleGeneratedImageCandidate;
pub use puzzle_generated_images_save_input_type::PuzzleGeneratedImagesSaveInput;
@@ -2188,6 +2192,7 @@ pub struct DbUpdate {
puzzle_agent_message: __sdk::TableUpdate<PuzzleAgentMessageRow>,
puzzle_agent_session: __sdk::TableUpdate<PuzzleAgentSessionRow>,
puzzle_event: __sdk::TableUpdate<PuzzleEvent>,
puzzle_gallery_card_view: __sdk::TableUpdate<PuzzleGalleryCardViewRow>,
puzzle_gallery_view: __sdk::TableUpdate<PuzzleWorkProfile>,
puzzle_leaderboard_entry: __sdk::TableUpdate<PuzzleLeaderboardEntryRow>,
puzzle_runtime_run: __sdk::TableUpdate<PuzzleRuntimeRunRow>,
@@ -2432,6 +2437,9 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
"puzzle_event" => db_update
.puzzle_event
.append(puzzle_event_table::parse_table_update(table_update)?),
"puzzle_gallery_card_view" => db_update.puzzle_gallery_card_view.append(
puzzle_gallery_card_view_table::parse_table_update(table_update)?,
),
"puzzle_gallery_view" => db_update
.puzzle_gallery_view
.append(puzzle_gallery_view_table::parse_table_update(table_update)?),
@@ -3004,6 +3012,10 @@ impl __sdk::DbUpdate for DbUpdate {
"match_3_d_gallery_view",
&self.match_3_d_gallery_view,
);
diff.puzzle_gallery_card_view = cache.apply_diff_to_table::<PuzzleGalleryCardViewRow>(
"puzzle_gallery_card_view",
&self.puzzle_gallery_card_view,
);
diff.puzzle_gallery_view = cache.apply_diff_to_table::<PuzzleWorkProfile>(
"puzzle_gallery_view",
&self.puzzle_gallery_view,
@@ -3221,6 +3233,9 @@ impl __sdk::DbUpdate for DbUpdate {
"puzzle_event" => db_update
.puzzle_event
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"puzzle_gallery_card_view" => db_update
.puzzle_gallery_card_view
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"puzzle_gallery_view" => db_update
.puzzle_gallery_view
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
@@ -3516,6 +3531,9 @@ impl __sdk::DbUpdate for DbUpdate {
"puzzle_event" => db_update
.puzzle_event
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"puzzle_gallery_card_view" => db_update
.puzzle_gallery_card_view
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"puzzle_gallery_view" => db_update
.puzzle_gallery_view
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
@@ -3683,6 +3701,7 @@ pub struct AppliedDiff<'r> {
puzzle_agent_message: __sdk::TableAppliedDiff<'r, PuzzleAgentMessageRow>,
puzzle_agent_session: __sdk::TableAppliedDiff<'r, PuzzleAgentSessionRow>,
puzzle_event: __sdk::TableAppliedDiff<'r, PuzzleEvent>,
puzzle_gallery_card_view: __sdk::TableAppliedDiff<'r, PuzzleGalleryCardViewRow>,
puzzle_gallery_view: __sdk::TableAppliedDiff<'r, PuzzleWorkProfile>,
puzzle_leaderboard_entry: __sdk::TableAppliedDiff<'r, PuzzleLeaderboardEntryRow>,
puzzle_runtime_run: __sdk::TableAppliedDiff<'r, PuzzleRuntimeRunRow>,
@@ -4043,6 +4062,11 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
&self.puzzle_event,
event,
);
callbacks.invoke_table_row_callbacks::<PuzzleGalleryCardViewRow>(
"puzzle_gallery_card_view",
&self.puzzle_gallery_card_view,
event,
);
callbacks.invoke_table_row_callbacks::<PuzzleWorkProfile>(
"puzzle_gallery_view",
&self.puzzle_gallery_view,
@@ -4901,6 +4925,7 @@ impl __sdk::SpacetimeModule for RemoteModule {
puzzle_agent_message_table::register_table(client_cache);
puzzle_agent_session_table::register_table(client_cache);
puzzle_event_table::register_table(client_cache);
puzzle_gallery_card_view_table::register_table(client_cache);
puzzle_gallery_view_table::register_table(client_cache);
puzzle_leaderboard_entry_table::register_table(client_cache);
puzzle_runtime_run_table::register_table(client_cache);
@@ -4997,6 +5022,7 @@ impl __sdk::SpacetimeModule for RemoteModule {
"puzzle_agent_message",
"puzzle_agent_session",
"puzzle_event",
"puzzle_gallery_card_view",
"puzzle_gallery_view",
"puzzle_leaderboard_entry",
"puzzle_runtime_run",

View File

@@ -0,0 +1,110 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::puzzle_publication_status_type::PuzzlePublicationStatus;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
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>,
}
impl __sdk::InModule for PuzzleGalleryCardViewRow {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `PuzzleGalleryCardViewRow`.
///
/// Provides typed access to columns for query building.
pub struct PuzzleGalleryCardViewRowCols {
pub work_id: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub profile_id: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub owner_user_id: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub source_session_id: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, Option<String>>,
pub author_display_name: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub work_title: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub work_description: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub level_name: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub summary: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, String>,
pub theme_tags: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, Vec<String>>,
pub cover_image_src: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, Option<String>>,
pub cover_asset_id: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, Option<String>>,
pub publication_status:
__sdk::__query_builder::Col<PuzzleGalleryCardViewRow, PuzzlePublicationStatus>,
pub updated_at_micros: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, i64>,
pub published_at_micros: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, Option<i64>>,
pub play_count: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, u32>,
pub remix_count: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, u32>,
pub like_count: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, u32>,
pub point_incentive_total_half_points:
__sdk::__query_builder::Col<PuzzleGalleryCardViewRow, u64>,
pub point_incentive_claimed_points: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, u64>,
pub publish_ready: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, bool>,
pub generation_status: __sdk::__query_builder::Col<PuzzleGalleryCardViewRow, Option<String>>,
}
impl __sdk::__query_builder::HasCols for PuzzleGalleryCardViewRow {
type Cols = PuzzleGalleryCardViewRowCols;
fn cols(table_name: &'static str) -> Self::Cols {
PuzzleGalleryCardViewRowCols {
work_id: __sdk::__query_builder::Col::new(table_name, "work_id"),
profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"),
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
source_session_id: __sdk::__query_builder::Col::new(table_name, "source_session_id"),
author_display_name: __sdk::__query_builder::Col::new(
table_name,
"author_display_name",
),
work_title: __sdk::__query_builder::Col::new(table_name, "work_title"),
work_description: __sdk::__query_builder::Col::new(table_name, "work_description"),
level_name: __sdk::__query_builder::Col::new(table_name, "level_name"),
summary: __sdk::__query_builder::Col::new(table_name, "summary"),
theme_tags: __sdk::__query_builder::Col::new(table_name, "theme_tags"),
cover_image_src: __sdk::__query_builder::Col::new(table_name, "cover_image_src"),
cover_asset_id: __sdk::__query_builder::Col::new(table_name, "cover_asset_id"),
publication_status: __sdk::__query_builder::Col::new(table_name, "publication_status"),
updated_at_micros: __sdk::__query_builder::Col::new(table_name, "updated_at_micros"),
published_at_micros: __sdk::__query_builder::Col::new(
table_name,
"published_at_micros",
),
play_count: __sdk::__query_builder::Col::new(table_name, "play_count"),
remix_count: __sdk::__query_builder::Col::new(table_name, "remix_count"),
like_count: __sdk::__query_builder::Col::new(table_name, "like_count"),
point_incentive_total_half_points: __sdk::__query_builder::Col::new(
table_name,
"point_incentive_total_half_points",
),
point_incentive_claimed_points: __sdk::__query_builder::Col::new(
table_name,
"point_incentive_claimed_points",
),
publish_ready: __sdk::__query_builder::Col::new(table_name, "publish_ready"),
generation_status: __sdk::__query_builder::Col::new(table_name, "generation_status"),
}
}
}

View File

@@ -0,0 +1,115 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#![allow(unused, clippy::all)]
use super::puzzle_gallery_card_view_row_type::PuzzleGalleryCardViewRow;
use super::puzzle_publication_status_type::PuzzlePublicationStatus;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `puzzle_gallery_card_view`.
///
/// Obtain a handle from the [`PuzzleGalleryCardViewTableAccess::puzzle_gallery_card_view`] method on [`super::RemoteTables`],
/// like `ctx.db.puzzle_gallery_card_view()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.puzzle_gallery_card_view().on_insert(...)`.
pub struct PuzzleGalleryCardViewTableHandle<'ctx> {
imp: __sdk::TableHandle<PuzzleGalleryCardViewRow>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `puzzle_gallery_card_view`.
///
/// Implemented for [`super::RemoteTables`].
pub trait PuzzleGalleryCardViewTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`PuzzleGalleryCardViewTableHandle`], which mediates access to the table `puzzle_gallery_card_view`.
fn puzzle_gallery_card_view(&self) -> PuzzleGalleryCardViewTableHandle<'_>;
}
impl PuzzleGalleryCardViewTableAccess for super::RemoteTables {
fn puzzle_gallery_card_view(&self) -> PuzzleGalleryCardViewTableHandle<'_> {
PuzzleGalleryCardViewTableHandle {
imp: self
.imp
.get_table::<PuzzleGalleryCardViewRow>("puzzle_gallery_card_view"),
ctx: std::marker::PhantomData,
}
}
}
pub struct PuzzleGalleryCardViewInsertCallbackId(__sdk::CallbackId);
pub struct PuzzleGalleryCardViewDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::Table for PuzzleGalleryCardViewTableHandle<'ctx> {
type Row = PuzzleGalleryCardViewRow;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = PuzzleGalleryCardViewRow> + '_ {
self.imp.iter()
}
type InsertCallbackId = PuzzleGalleryCardViewInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> PuzzleGalleryCardViewInsertCallbackId {
PuzzleGalleryCardViewInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: PuzzleGalleryCardViewInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = PuzzleGalleryCardViewDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> PuzzleGalleryCardViewDeleteCallbackId {
PuzzleGalleryCardViewDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: PuzzleGalleryCardViewDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table =
client_cache.get_or_make_table::<PuzzleGalleryCardViewRow>("puzzle_gallery_card_view");
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<PuzzleGalleryCardViewRow>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<PuzzleGalleryCardViewRow>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `PuzzleGalleryCardViewRow`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait puzzle_gallery_card_viewQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `PuzzleGalleryCardViewRow`.
fn puzzle_gallery_card_view(&self) -> __sdk::__query_builder::Table<PuzzleGalleryCardViewRow>;
}
impl puzzle_gallery_card_viewQueryTableAccess for __sdk::QueryTableAccessor {
fn puzzle_gallery_card_view(&self) -> __sdk::__query_builder::Table<PuzzleGalleryCardViewRow> {
__sdk::__query_builder::Table::new("puzzle_gallery_card_view")
}
}

View File

@@ -402,24 +402,28 @@ impl SpacetimeClient {
pub async fn list_puzzle_gallery(
&self,
) -> Result<Vec<PuzzleWorkProfileRecord>, SpacetimeClientError> {
) -> Result<Vec<PuzzleGalleryCardRecord>, SpacetimeClientError> {
self.read_after_connect(move |connection| {
let mut items = connection
.db()
.puzzle_gallery_view()
.puzzle_gallery_card_view()
.iter()
.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))
});
let recent_play_counts = public_work_recent_play_counts(connection, "puzzle");
Ok(items
.into_iter()
.map(|item| {
let mut record = map_puzzle_work_profile_row(item);
record.recent_play_count_7d = recent_play_counts
.get(&record.profile_id)
let recent_play_count_7d = recent_play_counts
.get(&item.profile_id)
.copied()
.unwrap_or(0);
record
map_puzzle_gallery_card_view_row(item, recent_play_count_7d)
})
.collect())
})