Files
Genarrative/server-rs/crates/spacetime-module/src/runtime/browse_history.rs
2026-05-16 22:52:10 +08:00

221 lines
7.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::*;
#[spacetimedb::table(
accessor = user_browse_history,
index(accessor = by_browse_history_user_id, btree(columns = [user_id])),
index(
accessor = by_browse_history_user_owner_profile,
btree(columns = [user_id, owner_user_id, profile_id])
)
)]
pub struct UserBrowseHistory {
#[primary_key]
pub(crate) browse_history_id: String,
pub(crate) user_id: String,
pub(crate) owner_user_id: String,
pub(crate) profile_id: String,
pub(crate) world_name: String,
pub(crate) subtitle: String,
pub(crate) summary_text: String,
pub(crate) cover_image_src: Option<String>,
pub(crate) theme_mode: RuntimeBrowseHistoryThemeMode,
pub(crate) author_display_name: String,
pub(crate) visited_at: Timestamp,
pub(crate) created_at: Timestamp,
pub(crate) updated_at: Timestamp,
}
// procedure 面向 Axum 同步拉取浏览历史,继续沿用旧 Node 的 visitedAt 倒序输出语义。
#[spacetimedb::procedure]
pub fn list_platform_browse_history(
ctx: &mut ProcedureContext,
input: RuntimeBrowseHistoryListInput,
) -> RuntimeBrowseHistoryProcedureResult {
match ctx.try_with_tx(|tx| list_platform_browse_history_rows(tx, input.clone())) {
Ok(entries) => RuntimeBrowseHistoryProcedureResult {
ok: true,
entries,
error_message: None,
},
Err(message) => RuntimeBrowseHistoryProcedureResult {
ok: false,
entries: Vec::new(),
error_message: Some(message),
},
}
}
// procedure 面向 Axum 承接 browse history 的单条/批量 POST同步返回当前用户的完整列表。
#[spacetimedb::procedure]
pub fn upsert_platform_browse_history_and_return(
ctx: &mut ProcedureContext,
input: RuntimeBrowseHistorySyncInput,
) -> RuntimeBrowseHistoryProcedureResult {
match ctx.try_with_tx(|tx| upsert_platform_browse_history_rows(tx, input.clone())) {
Ok(entries) => RuntimeBrowseHistoryProcedureResult {
ok: true,
entries,
error_message: None,
},
Err(message) => RuntimeBrowseHistoryProcedureResult {
ok: false,
entries: Vec::new(),
error_message: Some(message),
},
}
}
// procedure 面向 Axum 清空当前用户浏览历史,并直接返回空列表响应。
#[spacetimedb::procedure]
pub fn clear_platform_browse_history_and_return(
ctx: &mut ProcedureContext,
input: RuntimeBrowseHistoryClearInput,
) -> RuntimeBrowseHistoryProcedureResult {
match ctx.try_with_tx(|tx| clear_platform_browse_history_rows(tx, input.clone())) {
Ok(entries) => RuntimeBrowseHistoryProcedureResult {
ok: true,
entries,
error_message: None,
},
Err(message) => RuntimeBrowseHistoryProcedureResult {
ok: false,
entries: Vec::new(),
error_message: Some(message),
},
}
}
fn list_platform_browse_history_rows(
ctx: &ReducerContext,
input: RuntimeBrowseHistoryListInput,
) -> Result<Vec<RuntimeBrowseHistorySnapshot>, String> {
let validated_input = build_runtime_browse_history_list_input(input.user_id)
.map_err(|error| error.to_string())?;
let mut entries = ctx
.db
.user_browse_history()
.by_browse_history_user_id()
.filter(&validated_input.user_id)
.map(|row| build_runtime_browse_history_snapshot_from_row(&row))
.collect::<Vec<_>>();
entries.sort_by(|left, right| {
right
.visited_at_micros
.cmp(&left.visited_at_micros)
.then_with(|| left.browse_history_id.cmp(&right.browse_history_id))
});
Ok(entries)
}
fn upsert_platform_browse_history_rows(
ctx: &ReducerContext,
input: RuntimeBrowseHistorySyncInput,
) -> Result<Vec<RuntimeBrowseHistorySnapshot>, String> {
let user_id = input.user_id.clone();
let prepared_entries =
prepare_runtime_browse_history_entries(input).map_err(|error| error.to_string())?;
for prepared in prepared_entries {
let existing = ctx
.db
.user_browse_history()
.browse_history_id()
.find(&prepared.browse_history_id);
let created_at = existing
.as_ref()
.map(|row| row.created_at)
.unwrap_or_else(|| Timestamp::from_micros_since_unix_epoch(prepared.updated_at_micros));
if let Some(existing) = existing {
ctx.db
.user_browse_history()
.browse_history_id()
.delete(&existing.browse_history_id);
}
ctx.db.user_browse_history().insert(UserBrowseHistory {
browse_history_id: prepared.browse_history_id,
user_id: prepared.user_id,
owner_user_id: prepared.owner_user_id,
profile_id: prepared.profile_id,
world_name: prepared.world_name,
subtitle: prepared.subtitle,
summary_text: prepared.summary_text,
cover_image_src: prepared.cover_image_src,
theme_mode: prepared.theme_mode,
author_display_name: prepared.author_display_name,
visited_at: Timestamp::from_micros_since_unix_epoch(prepared.visited_at_micros),
created_at,
updated_at: Timestamp::from_micros_since_unix_epoch(prepared.updated_at_micros),
});
}
list_platform_browse_history_rows(ctx, RuntimeBrowseHistoryListInput { user_id })
}
fn clear_platform_browse_history_rows(
ctx: &ReducerContext,
input: RuntimeBrowseHistoryClearInput,
) -> Result<Vec<RuntimeBrowseHistorySnapshot>, String> {
let validated_input = build_runtime_browse_history_clear_input(input.user_id)
.map_err(|error| error.to_string())?;
let row_ids = ctx
.db
.user_browse_history()
.by_browse_history_user_id()
.filter(&validated_input.user_id)
.map(|row| row.browse_history_id.clone())
.collect::<Vec<_>>();
for row_id in row_ids {
ctx.db
.user_browse_history()
.browse_history_id()
.delete(&row_id);
}
Ok(Vec::new())
}
fn build_runtime_browse_history_snapshot_from_row(
row: &UserBrowseHistory,
) -> RuntimeBrowseHistorySnapshot {
RuntimeBrowseHistorySnapshot {
browse_history_id: row.browse_history_id.clone(),
user_id: row.user_id.clone(),
owner_user_id: row.owner_user_id.clone(),
profile_id: row.profile_id.clone(),
world_name: row.world_name.clone(),
subtitle: row.subtitle.clone(),
summary_text: row.summary_text.clone(),
cover_image_src: row.cover_image_src.clone(),
theme_mode: row.theme_mode,
author_display_name: row.author_display_name.clone(),
visited_at_micros: row.visited_at.to_micros_since_unix_epoch(),
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
}
}
#[allow(dead_code)]
fn build_runtime_browse_history_row(snapshot: RuntimeBrowseHistorySnapshot) -> UserBrowseHistory {
UserBrowseHistory {
browse_history_id: snapshot.browse_history_id,
user_id: snapshot.user_id,
owner_user_id: snapshot.owner_user_id,
profile_id: snapshot.profile_id,
world_name: snapshot.world_name,
subtitle: snapshot.subtitle,
summary_text: snapshot.summary_text,
cover_image_src: snapshot.cover_image_src,
theme_mode: snapshot.theme_mode,
author_display_name: snapshot.author_display_name,
visited_at: Timestamp::from_micros_since_unix_epoch(snapshot.visited_at_micros),
created_at: Timestamp::from_micros_since_unix_epoch(snapshot.created_at_micros),
updated_at: Timestamp::from_micros_since_unix_epoch(snapshot.updated_at_micros),
}
}