221 lines
7.8 KiB
Rust
221 lines
7.8 KiB
Rust
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),
|
||
}
|
||
}
|