推进 SpacetimeDB adapter 与 client 收口
This commit is contained in:
@@ -66,7 +66,6 @@ fn upsert_asset_entity_binding(
|
||||
return Err("asset_entity_binding.asset_object_id 对应的 asset_object 不存在".to_string());
|
||||
}
|
||||
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(input.updated_at_micros);
|
||||
// 首版绑定按 entity_kind + entity_id + slot 幂等定位,后续访问量明确后再改为组合索引扫描。
|
||||
let current = ctx.db.asset_entity_binding().iter().find(|row| {
|
||||
row.entity_kind == input.entity_kind
|
||||
@@ -80,7 +79,7 @@ fn upsert_asset_entity_binding(
|
||||
.asset_entity_binding()
|
||||
.binding_id()
|
||||
.delete(&existing.binding_id);
|
||||
let row = AssetEntityBinding {
|
||||
let snapshot = AssetEntityBindingSnapshot {
|
||||
binding_id: existing.binding_id.clone(),
|
||||
asset_object_id: input.asset_object_id.clone(),
|
||||
entity_kind: input.entity_kind.clone(),
|
||||
@@ -89,27 +88,16 @@ fn upsert_asset_entity_binding(
|
||||
asset_kind: input.asset_kind.clone(),
|
||||
owner_user_id: input.owner_user_id.clone(),
|
||||
profile_id: input.profile_id.clone(),
|
||||
created_at: existing.created_at,
|
||||
updated_at,
|
||||
};
|
||||
ctx.db.asset_entity_binding().insert(row);
|
||||
|
||||
AssetEntityBindingSnapshot {
|
||||
binding_id: existing.binding_id,
|
||||
asset_object_id: input.asset_object_id,
|
||||
entity_kind: input.entity_kind,
|
||||
entity_id: input.entity_id,
|
||||
slot: input.slot,
|
||||
asset_kind: input.asset_kind,
|
||||
owner_user_id: input.owner_user_id,
|
||||
profile_id: input.profile_id,
|
||||
created_at_micros: existing.created_at.to_micros_since_unix_epoch(),
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
}
|
||||
};
|
||||
ctx.db
|
||||
.asset_entity_binding()
|
||||
.insert(build_asset_entity_binding_row(&snapshot));
|
||||
snapshot
|
||||
}
|
||||
None => {
|
||||
let created_at = updated_at;
|
||||
let row = AssetEntityBinding {
|
||||
let snapshot = AssetEntityBindingSnapshot {
|
||||
binding_id: input.binding_id.clone(),
|
||||
asset_object_id: input.asset_object_id.clone(),
|
||||
entity_kind: input.entity_kind.clone(),
|
||||
@@ -118,25 +106,30 @@ fn upsert_asset_entity_binding(
|
||||
asset_kind: input.asset_kind.clone(),
|
||||
owner_user_id: input.owner_user_id.clone(),
|
||||
profile_id: input.profile_id.clone(),
|
||||
created_at,
|
||||
updated_at,
|
||||
};
|
||||
ctx.db.asset_entity_binding().insert(row);
|
||||
|
||||
AssetEntityBindingSnapshot {
|
||||
binding_id: input.binding_id,
|
||||
asset_object_id: input.asset_object_id,
|
||||
entity_kind: input.entity_kind,
|
||||
entity_id: input.entity_id,
|
||||
slot: input.slot,
|
||||
asset_kind: input.asset_kind,
|
||||
owner_user_id: input.owner_user_id,
|
||||
profile_id: input.profile_id,
|
||||
created_at_micros: input.updated_at_micros,
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
}
|
||||
};
|
||||
ctx.db
|
||||
.asset_entity_binding()
|
||||
.insert(build_asset_entity_binding_row(&snapshot));
|
||||
snapshot
|
||||
}
|
||||
};
|
||||
|
||||
Ok(snapshot)
|
||||
}
|
||||
|
||||
fn build_asset_entity_binding_row(snapshot: &AssetEntityBindingSnapshot) -> AssetEntityBinding {
|
||||
AssetEntityBinding {
|
||||
binding_id: snapshot.binding_id.clone(),
|
||||
asset_object_id: snapshot.asset_object_id.clone(),
|
||||
entity_kind: snapshot.entity_kind.clone(),
|
||||
entity_id: snapshot.entity_id.clone(),
|
||||
slot: snapshot.slot.clone(),
|
||||
asset_kind: snapshot.asset_kind.clone(),
|
||||
owner_user_id: snapshot.owner_user_id.clone(),
|
||||
profile_id: snapshot.profile_id.clone(),
|
||||
created_at: Timestamp::from_micros_since_unix_epoch(snapshot.created_at_micros),
|
||||
updated_at: Timestamp::from_micros_since_unix_epoch(snapshot.updated_at_micros),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,6 @@ pub(crate) fn upsert_asset_object(
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(input.updated_at_micros);
|
||||
// 这里先保持最小可发布实现:查重语义已经冻结,后续再把实现优化回组合索引扫描。
|
||||
let current = ctx
|
||||
.db
|
||||
@@ -105,7 +104,7 @@ pub(crate) fn upsert_asset_object(
|
||||
.asset_object()
|
||||
.asset_object_id()
|
||||
.delete(&existing.asset_object_id);
|
||||
let row = AssetObject {
|
||||
let snapshot = AssetObjectUpsertSnapshot {
|
||||
asset_object_id: existing.asset_object_id.clone(),
|
||||
bucket: input.bucket.clone(),
|
||||
object_key: input.object_key.clone(),
|
||||
@@ -119,32 +118,16 @@ pub(crate) fn upsert_asset_object(
|
||||
profile_id: input.profile_id.clone(),
|
||||
entity_id: input.entity_id.clone(),
|
||||
asset_kind: input.asset_kind.clone(),
|
||||
created_at: existing.created_at,
|
||||
updated_at,
|
||||
};
|
||||
ctx.db.asset_object().insert(row);
|
||||
|
||||
AssetObjectUpsertSnapshot {
|
||||
asset_object_id: existing.asset_object_id,
|
||||
bucket: input.bucket,
|
||||
object_key: input.object_key,
|
||||
access_policy: input.access_policy,
|
||||
content_type: input.content_type,
|
||||
content_length: input.content_length,
|
||||
content_hash: input.content_hash,
|
||||
version: input.version,
|
||||
source_job_id: input.source_job_id,
|
||||
owner_user_id: input.owner_user_id,
|
||||
profile_id: input.profile_id,
|
||||
entity_id: input.entity_id,
|
||||
asset_kind: input.asset_kind,
|
||||
created_at_micros: existing.created_at.to_micros_since_unix_epoch(),
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
}
|
||||
};
|
||||
ctx.db
|
||||
.asset_object()
|
||||
.insert(build_asset_object_row(&snapshot));
|
||||
snapshot
|
||||
}
|
||||
None => {
|
||||
let created_at = updated_at;
|
||||
let row = AssetObject {
|
||||
let snapshot = AssetObjectUpsertSnapshot {
|
||||
asset_object_id: input.asset_object_id.clone(),
|
||||
bucket: input.bucket.clone(),
|
||||
object_key: input.object_key.clone(),
|
||||
@@ -158,28 +141,13 @@ pub(crate) fn upsert_asset_object(
|
||||
profile_id: input.profile_id.clone(),
|
||||
entity_id: input.entity_id.clone(),
|
||||
asset_kind: input.asset_kind.clone(),
|
||||
created_at,
|
||||
updated_at,
|
||||
};
|
||||
ctx.db.asset_object().insert(row);
|
||||
|
||||
AssetObjectUpsertSnapshot {
|
||||
asset_object_id: input.asset_object_id,
|
||||
bucket: input.bucket,
|
||||
object_key: input.object_key,
|
||||
access_policy: input.access_policy,
|
||||
content_type: input.content_type,
|
||||
content_length: input.content_length,
|
||||
content_hash: input.content_hash,
|
||||
version: input.version,
|
||||
source_job_id: input.source_job_id,
|
||||
owner_user_id: input.owner_user_id,
|
||||
profile_id: input.profile_id,
|
||||
entity_id: input.entity_id,
|
||||
asset_kind: input.asset_kind,
|
||||
created_at_micros: input.updated_at_micros,
|
||||
updated_at_micros: input.updated_at_micros,
|
||||
}
|
||||
};
|
||||
ctx.db
|
||||
.asset_object()
|
||||
.insert(build_asset_object_row(&snapshot));
|
||||
snapshot
|
||||
}
|
||||
};
|
||||
|
||||
@@ -244,3 +212,23 @@ fn object_key_to_legacy_image_src(object_key: &str) -> String {
|
||||
}
|
||||
format!("/{normalized}")
|
||||
}
|
||||
|
||||
fn build_asset_object_row(snapshot: &AssetObjectUpsertSnapshot) -> AssetObject {
|
||||
AssetObject {
|
||||
asset_object_id: snapshot.asset_object_id.clone(),
|
||||
bucket: snapshot.bucket.clone(),
|
||||
object_key: snapshot.object_key.clone(),
|
||||
access_policy: snapshot.access_policy,
|
||||
content_type: snapshot.content_type.clone(),
|
||||
content_length: snapshot.content_length,
|
||||
content_hash: snapshot.content_hash.clone(),
|
||||
version: snapshot.version,
|
||||
source_job_id: snapshot.source_job_id.clone(),
|
||||
owner_user_id: snapshot.owner_user_id.clone(),
|
||||
profile_id: snapshot.profile_id.clone(),
|
||||
entity_id: snapshot.entity_id.clone(),
|
||||
asset_kind: snapshot.asset_kind.clone(),
|
||||
created_at: Timestamp::from_micros_since_unix_epoch(snapshot.created_at_micros),
|
||||
updated_at: Timestamp::from_micros_since_unix_epoch(snapshot.updated_at_micros),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// 中文注释:SpacetimeDB 绑定生成依赖根模块继续公开 re-export 各领域类型;
|
||||
// 少数领域 helper 同名只影响 value namespace 导出,不影响 table / reducer 类型。
|
||||
#![allow(ambiguous_glob_reexports)]
|
||||
|
||||
pub use module_ai::*;
|
||||
pub use module_assets::*;
|
||||
pub use module_big_fish::*;
|
||||
|
||||
@@ -5,7 +5,8 @@ use spacetimedb_lib::sats::ser::serde::SerializeWrapper;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::puzzle::{
|
||||
puzzle_agent_message, puzzle_agent_session, puzzle_runtime_run, puzzle_work_profile,
|
||||
puzzle_agent_message, puzzle_agent_session, puzzle_event, puzzle_leaderboard_entry,
|
||||
puzzle_runtime_run, puzzle_work_profile,
|
||||
};
|
||||
|
||||
const MIGRATION_SCHEMA_VERSION: u32 = 1;
|
||||
@@ -140,7 +141,9 @@ macro_rules! migration_tables {
|
||||
puzzle_agent_session,
|
||||
puzzle_agent_message,
|
||||
puzzle_work_profile,
|
||||
puzzle_event,
|
||||
puzzle_runtime_run,
|
||||
puzzle_leaderboard_entry,
|
||||
big_fish_creation_session,
|
||||
big_fish_agent_message,
|
||||
big_fish_asset_slot,
|
||||
|
||||
@@ -18,7 +18,7 @@ use module_puzzle::{
|
||||
};
|
||||
use serde_json::from_str as json_from_str;
|
||||
use serde_json::to_string as json_to_string;
|
||||
use spacetimedb::{ProcedureContext, Table, Timestamp, TxContext};
|
||||
use spacetimedb::{ProcedureContext, SpacetimeType, Table, Timestamp, TxContext};
|
||||
|
||||
/// 拼图 Agent session 真相表。
|
||||
/// 当前只保存结构化字段与 JSON 草稿,不提前拆出更多编辑态子表。
|
||||
@@ -84,6 +84,33 @@ pub struct PuzzleWorkProfileRow {
|
||||
published_at: Option<Timestamp>,
|
||||
}
|
||||
|
||||
/// 拼图创作事件类型。
|
||||
///
|
||||
/// 事件表只广播跨层订阅需要的轻量事实,作品真相仍以
|
||||
/// `puzzle_work_profile` 和 `puzzle_agent_session` 为准。
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, SpacetimeType)]
|
||||
pub enum PuzzleEventKind {
|
||||
WorkPublished,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = puzzle_event,
|
||||
public,
|
||||
event,
|
||||
index(accessor = by_puzzle_event_profile_id, btree(columns = [profile_id])),
|
||||
index(accessor = by_puzzle_event_owner_user_id, btree(columns = [owner_user_id]))
|
||||
)]
|
||||
pub struct PuzzleEvent {
|
||||
#[primary_key]
|
||||
event_id: String,
|
||||
profile_id: String,
|
||||
work_id: String,
|
||||
session_id: Option<String>,
|
||||
owner_user_id: String,
|
||||
event_kind: PuzzleEventKind,
|
||||
occurred_at: Timestamp,
|
||||
}
|
||||
|
||||
/// 运行态 run 快照表。
|
||||
#[spacetimedb::table(
|
||||
accessor = puzzle_runtime_run,
|
||||
@@ -869,6 +896,7 @@ fn publish_puzzle_work_tx(
|
||||
updated_at: Timestamp::from_micros_since_unix_epoch(input.published_at_micros),
|
||||
},
|
||||
);
|
||||
emit_puzzle_work_published_event(ctx, &profile, input.published_at_micros);
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
@@ -1543,6 +1571,25 @@ fn upsert_puzzle_work_profile(ctx: &TxContext, profile: PuzzleWorkProfile) -> Re
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_puzzle_work_published_event(
|
||||
ctx: &TxContext,
|
||||
profile: &PuzzleWorkProfile,
|
||||
occurred_at_micros: i64,
|
||||
) {
|
||||
ctx.db.puzzle_event().insert(PuzzleEvent {
|
||||
event_id: format!(
|
||||
"pzevt_{}_{}_published",
|
||||
profile.profile_id, occurred_at_micros
|
||||
),
|
||||
profile_id: profile.profile_id.clone(),
|
||||
work_id: profile.work_id.clone(),
|
||||
session_id: profile.source_session_id.clone(),
|
||||
owner_user_id: profile.owner_user_id.clone(),
|
||||
event_kind: PuzzleEventKind::WorkPublished,
|
||||
occurred_at: Timestamp::from_micros_since_unix_epoch(occurred_at_micros),
|
||||
});
|
||||
}
|
||||
|
||||
fn insert_puzzle_runtime_run(
|
||||
ctx: &TxContext,
|
||||
run: &PuzzleRunSnapshot,
|
||||
|
||||
Reference in New Issue
Block a user