账户系统完善,使用uuid+多identity记录

This commit is contained in:
2026-04-20 03:28:03 +00:00
parent d06b3ad38c
commit d2a059d57a
26 changed files with 832 additions and 431 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -57,8 +57,9 @@ pub fn normalize_saved_at_ms(saved_at_ms: u64, timestamp: Timestamp) -> u64 {
}
}
pub fn user_id_for_identity_hex(identity_hex: &str) -> String {
format!("user_{identity_hex}")
pub fn new_account_id(now_ms: u64, identity_hex: &str) -> String {
let suffix = identity_hex.get(..10).unwrap_or(identity_hex);
format!("acct_{now_ms:x}_{suffix}")
}
pub fn session_id_for_identity_hex(identity_hex: &str) -> String {
@@ -77,24 +78,24 @@ pub fn phone_identity_id(phone_number: &str) -> String {
format!("authi_phone_{}", phone_number.replace('+', ""))
}
pub fn custom_world_profile_key(user_id: &str, profile_id: &str) -> String {
format!("{user_id}:{profile_id}")
pub fn custom_world_profile_key(account_id: &str, profile_id: &str) -> String {
format!("{account_id}:{profile_id}")
}
pub fn custom_world_session_key(user_id: &str, session_id: &str) -> String {
format!("{user_id}:{session_id}")
pub fn custom_world_session_key(account_id: &str, session_id: &str) -> String {
format!("{account_id}:{session_id}")
}
pub fn profile_wallet_ledger_id(user_id: &str, source_key: &str) -> String {
format!("ledger:{user_id}:{source_key}")
pub fn profile_wallet_ledger_id(account_id: &str, source_key: &str) -> String {
format!("ledger:{account_id}:{source_key}")
}
pub fn profile_played_world_key(user_id: &str, world_key: &str) -> String {
format!("played:{user_id}:{world_key}")
pub fn profile_played_world_key(account_id: &str, world_key: &str) -> String {
format!("played:{account_id}:{world_key}")
}
pub fn browse_history_key(user_id: &str, owner_user_id: &str, profile_id: &str) -> String {
format!("browse:{user_id}:{owner_user_id}:{profile_id}")
pub fn browse_history_key(account_id: &str, owner_account_id: &str, profile_id: &str) -> String {
format!("browse:{account_id}:{owner_account_id}:{profile_id}")
}
pub fn ip_key(ip: Option<&String>) -> String {
@@ -254,7 +255,7 @@ pub fn dedupe_browse_history_entries(
let mut deduped = Vec::new();
let mut seen_keys = std::collections::BTreeSet::new();
for entry in normalized_entries {
let key = format!("{}:{}", entry.owner_user_id, entry.profile_id);
let key = format!("{}:{}", entry.owner_account_id, entry.profile_id);
if seen_keys.insert(key) {
deduped.push(entry);
}
@@ -266,15 +267,15 @@ fn normalize_browse_history_entry(
entry: PlatformBrowseHistoryWriteInput,
now_ms: u64,
) -> Option<PlatformBrowseHistoryWriteInput> {
let owner_user_id = normalize_required_string(&entry.owner_user_id);
let owner_account_id = normalize_required_string(&entry.owner_account_id);
let profile_id = normalize_required_string(&entry.profile_id);
let world_name = normalize_required_string(&entry.world_name);
if owner_user_id.is_empty() || profile_id.is_empty() || world_name.is_empty() {
if owner_account_id.is_empty() || profile_id.is_empty() || world_name.is_empty() {
return None;
}
Some(PlatformBrowseHistoryWriteInput {
owner_user_id,
owner_account_id,
profile_id,
world_name,
subtitle: normalize_required_string(&entry.subtitle),

View File

@@ -15,10 +15,15 @@ pub fn init(ctx: &ReducerContext) {
#[reducer(client_connected)]
pub fn client_connected(ctx: &ReducerContext) {
config::ensure_default_app_config(ctx);
let provisioned = auth::provision_user(ctx);
if provisioned.existed && auth::needs_sms_verification(&provisioned.config, &provisioned.user)
let provisioned = auth::provision_account(ctx);
if provisioned.existed
&& auth::needs_sms_verification(&provisioned.config, &provisioned.account)
{
auth::emit_verification_prompt(ctx, &provisioned.user, "账号尚未完成短信验证,请先验证手机号");
auth::emit_verification_prompt(
ctx,
&provisioned.account,
"账号尚未完成短信验证,请先验证手机号",
);
}
}

View File

@@ -3,7 +3,7 @@ use spacetimedb::{
procedure, view, AnonymousViewContext, ProcedureContext, Table, TxContext, ViewContext,
};
use crate::auth::{find_user_by_identity_read_only, guard_user_action};
use crate::auth::{find_account_by_identity_read_only, guard_account_action};
use crate::common::{
browse_history_key, builtin_world_title, contains_any, custom_world_profile_key,
custom_world_session_key, dedupe_browse_history_entries, is_valid_json, normalize_required_string,
@@ -17,11 +17,11 @@ use crate::types::*;
#[view(accessor = my_snapshot, public)]
pub fn my_snapshot_view(ctx: &ViewContext) -> Option<SnapshotView> {
let user = find_user_by_identity_read_only(ctx.sender())?;
let account = find_account_by_identity_read_only(ctx.sender())?;
ctx.db
.saved_snapshot_row()
.user_id()
.find(&user.id)
.account_id()
.find(&account.id)
.map(|snapshot| SnapshotView {
version: snapshot.version,
saved_at_ms: snapshot.saved_at_ms,
@@ -33,13 +33,13 @@ pub fn my_snapshot_view(ctx: &ViewContext) -> Option<SnapshotView> {
#[view(accessor = my_runtime_settings, public)]
pub fn my_runtime_settings_view(ctx: &ViewContext) -> Option<RuntimeSettingsView> {
let user = find_user_by_identity_read_only(ctx.sender())?;
let account = find_account_by_identity_read_only(ctx.sender())?;
let config = load_app_config_read_only().unwrap_or_else(|| default_app_config(0));
let music_volume = ctx
.db
.runtime_setting()
.user_id()
.find(&user.id)
.account_id()
.find(&account.id)
.map(|row| row.music_volume)
.unwrap_or(config.default_music_volume);
Some(RuntimeSettingsView { music_volume })
@@ -47,13 +47,13 @@ pub fn my_runtime_settings_view(ctx: &ViewContext) -> Option<RuntimeSettingsView
#[view(accessor = my_profile_dashboard, public)]
pub fn my_profile_dashboard_view(ctx: &ViewContext) -> Option<ProfileDashboardView> {
let user = find_user_by_identity_read_only(ctx.sender())?;
let state = ctx.db.profile_dashboard_state().user_id().find(&user.id);
let account = find_account_by_identity_read_only(ctx.sender())?;
let state = ctx.db.profile_dashboard_state().account_id().find(&account.id);
let played_world_count = ctx
.db
.profile_played_world()
.user_id()
.filter(&user.id)
.account_id()
.filter(&account.id)
.count() as u32;
Some(ProfileDashboardView {
wallet_balance: state.as_ref().map(|row| row.wallet_balance).unwrap_or(0),
@@ -65,14 +65,14 @@ pub fn my_profile_dashboard_view(ctx: &ViewContext) -> Option<ProfileDashboardVi
#[view(accessor = my_profile_wallet_ledger, public)]
pub fn my_profile_wallet_ledger_view(ctx: &ViewContext) -> Vec<ProfileWalletLedgerView> {
let Some(user) = find_user_by_identity_read_only(ctx.sender()) else {
let Some(account) = find_account_by_identity_read_only(ctx.sender()) else {
return Vec::new();
};
let mut rows: Vec<_> = ctx
.db
.profile_wallet_ledger()
.user_id()
.filter(&user.id)
.account_id()
.filter(&account.id)
.map(to_profile_wallet_ledger_view)
.collect();
sort_desc_by_key(&mut rows, |row| row.created_at_ms);
@@ -82,14 +82,14 @@ pub fn my_profile_wallet_ledger_view(ctx: &ViewContext) -> Vec<ProfileWalletLedg
#[view(accessor = my_profile_played_worlds, public)]
pub fn my_profile_played_worlds_view(ctx: &ViewContext) -> Vec<ProfilePlayedWorldView> {
let Some(user) = find_user_by_identity_read_only(ctx.sender()) else {
let Some(account) = find_account_by_identity_read_only(ctx.sender()) else {
return Vec::new();
};
let mut rows: Vec<_> = ctx
.db
.profile_played_world()
.user_id()
.filter(&user.id)
.account_id()
.filter(&account.id)
.map(to_profile_played_world_view)
.collect();
sort_desc_by_key(&mut rows, |row| row.last_played_at_ms);
@@ -98,14 +98,14 @@ pub fn my_profile_played_worlds_view(ctx: &ViewContext) -> Vec<ProfilePlayedWorl
#[view(accessor = my_browse_history, public)]
pub fn my_browse_history_view(ctx: &ViewContext) -> Vec<PlatformBrowseHistoryView> {
let Some(user) = find_user_by_identity_read_only(ctx.sender()) else {
let Some(account) = find_account_by_identity_read_only(ctx.sender()) else {
return Vec::new();
};
let mut rows: Vec<_> = ctx
.db
.user_browse_history()
.user_id()
.filter(&user.id)
.account_browse_history()
.account_id()
.filter(&account.id)
.map(to_platform_browse_history_view)
.collect();
sort_desc_by_key(&mut rows, |row| row.visited_at_ms);
@@ -114,14 +114,14 @@ pub fn my_browse_history_view(ctx: &ViewContext) -> Vec<PlatformBrowseHistoryVie
#[view(accessor = my_custom_world_profiles, public)]
pub fn my_custom_world_profiles_view(ctx: &ViewContext) -> Vec<CustomWorldProfileView> {
let Some(user) = find_user_by_identity_read_only(ctx.sender()) else {
let Some(account) = find_account_by_identity_read_only(ctx.sender()) else {
return Vec::new();
};
let mut rows: Vec<_> = ctx
.db
.custom_world_profile()
.user_id()
.filter(&user.id)
.account_id()
.filter(&account.id)
.filter(|row| row.deleted_at_ms.is_none())
.map(to_custom_world_profile_view)
.collect();
@@ -164,7 +164,7 @@ pub fn published_custom_world_profiles_view(
.filter(&CustomWorldPublicationStatus::Published)
.filter(|row| row.deleted_at_ms.is_none())
.map(|row| PublishedCustomWorldProfileView {
owner_user_id: row.user_id,
owner_account_id: row.account_id,
profile_id: row.profile_id,
payload_json: row.payload_json,
visibility: row.visibility,
@@ -193,14 +193,14 @@ pub fn published_custom_world_profiles_view(
#[view(accessor = my_custom_world_sessions, public)]
pub fn my_custom_world_sessions_view(ctx: &ViewContext) -> Vec<CustomWorldSessionView> {
let Some(user) = find_user_by_identity_read_only(ctx.sender()) else {
let Some(account) = find_account_by_identity_read_only(ctx.sender()) else {
return Vec::new();
};
let mut rows: Vec<_> = ctx
.db
.custom_world_session()
.user_id()
.filter(&user.id)
.account_id()
.filter(&account.id)
.map(to_custom_world_session_view)
.collect();
sort_desc_by_key(&mut rows, |row| row.updated_at_ms);
@@ -216,17 +216,17 @@ pub fn save_snapshot(
bottom_tab: String,
current_story_json: Option<String>,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "save_snapshot") {
Ok(user) => save_snapshot_impl(tx, &user, saved_at_ms, game_state_json.clone(), bottom_tab.clone(), current_story_json.clone()),
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "save_snapshot") {
Ok(account) => save_snapshot_impl(tx, &account, saved_at_ms, game_state_json.clone(), bottom_tab.clone(), current_story_json.clone()),
Err(result) => result,
})
}
#[procedure]
pub fn delete_snapshot(ctx: &mut ProcedureContext, meta: RequestMeta) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "delete_snapshot") {
Ok(user) => {
tx.db.saved_snapshot_row().user_id().delete(&user.id);
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "delete_snapshot") {
Ok(account) => {
tx.db.saved_snapshot_row().account_id().delete(&account.id);
MutationResult::ok("snapshot_deleted", "运行时快照已删除")
}
Err(result) => result,
@@ -239,11 +239,11 @@ pub fn put_runtime_settings(
meta: RequestMeta,
music_volume: f32,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "put_runtime_settings") {
Ok(user) => {
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "put_runtime_settings") {
Ok(account) => {
let config = crate::config::ensure_app_config_row(tx);
let row = RuntimeSetting {
user_id: user.id,
account_id: account.id,
music_volume: if music_volume.is_finite() {
music_volume.clamp(0.0, 1.0)
} else {
@@ -264,15 +264,15 @@ pub fn upsert_platform_browse_history(
meta: RequestMeta,
entries: Vec<PlatformBrowseHistoryWriteInput>,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "upsert_platform_browse_history") {
Ok(user) => {
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "upsert_platform_browse_history") {
Ok(account) => {
let normalized_entries =
dedupe_browse_history_entries(entries.clone(), timestamp_ms(tx.timestamp));
for entry in normalized_entries {
let row = UserBrowseHistory {
id: browse_history_key(&user.id, &entry.owner_user_id, &entry.profile_id),
user_id: user.id.clone(),
owner_user_id: entry.owner_user_id,
let row = AccountBrowseHistory {
id: browse_history_key(&account.id, &entry.owner_account_id, &entry.profile_id),
account_id: account.id.clone(),
owner_account_id: entry.owner_account_id,
profile_id: entry.profile_id,
world_name: entry.world_name,
subtitle: entry.subtitle,
@@ -295,17 +295,17 @@ pub fn clear_platform_browse_history(
ctx: &mut ProcedureContext,
meta: RequestMeta,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "clear_platform_browse_history") {
Ok(user) => {
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "clear_platform_browse_history") {
Ok(account) => {
let keys: Vec<_> = tx
.db
.user_browse_history()
.user_id()
.filter(&user.id)
.account_browse_history()
.account_id()
.filter(&account.id)
.map(|row| row.id)
.collect();
for key in keys {
tx.db.user_browse_history().id().delete(&key);
tx.db.account_browse_history().id().delete(&key);
}
MutationResult::ok("browse_history_cleared", "浏览历史已清空")
}
@@ -321,10 +321,10 @@ pub fn upsert_custom_world_profile(
payload_json: String,
author_display_name: String,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "upsert_custom_world_profile") {
Ok(user) => upsert_custom_world_profile_impl(
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "upsert_custom_world_profile") {
Ok(account) => upsert_custom_world_profile_impl(
tx,
&user,
&account,
profile_id.clone(),
payload_json.clone(),
author_display_name.clone(),
@@ -339,10 +339,10 @@ pub fn delete_custom_world_profile(
meta: RequestMeta,
profile_id: String,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "delete_custom_world_profile") {
Ok(user) => {
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "delete_custom_world_profile") {
Ok(account) => {
let normalized_profile_id = normalize_required_string(&profile_id);
let key = custom_world_profile_key(&user.id, &normalized_profile_id);
let key = custom_world_profile_key(&account.id, &normalized_profile_id);
let Some(existing) = tx.db.custom_world_profile().id().find(&key) else {
return MutationResult::error("custom_world_not_found", "未找到目标自定义世界档案");
};
@@ -369,8 +369,8 @@ pub fn upsert_custom_world_session(
created_at_ms: u64,
updated_at_ms: u64,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "upsert_custom_world_session") {
Ok(user) => {
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "upsert_custom_world_session") {
Ok(account) => {
let normalized_session_id = normalize_required_string(&session_id);
if normalized_session_id.is_empty() {
return MutationResult::error("invalid_session_id", "sessionId 不能为空");
@@ -379,8 +379,8 @@ pub fn upsert_custom_world_session(
return MutationResult::error("invalid_session_json", "会话数据 JSON 不合法");
}
let row = CustomWorldSession {
id: custom_world_session_key(&user.id, &normalized_session_id),
user_id: user.id,
id: custom_world_session_key(&account.id, &normalized_session_id),
account_id: account.id,
session_id: normalized_session_id,
payload_json: payload_json.clone(),
created_at_ms: if created_at_ms == 0 {
@@ -408,10 +408,10 @@ pub fn publish_custom_world_profile(
profile_id: String,
author_display_name: String,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "publish_custom_world_profile") {
Ok(user) => publish_or_unpublish_custom_world_profile(
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "publish_custom_world_profile") {
Ok(account) => publish_or_unpublish_custom_world_profile(
tx,
&user,
&account,
profile_id.clone(),
author_display_name.clone(),
true,
@@ -427,10 +427,10 @@ pub fn unpublish_custom_world_profile(
profile_id: String,
author_display_name: String,
) -> MutationResult {
ctx.with_tx(|tx| match guard_user_action(tx, &meta, "unpublish_custom_world_profile") {
Ok(user) => publish_or_unpublish_custom_world_profile(
ctx.with_tx(|tx| match guard_account_action(tx, &meta, "unpublish_custom_world_profile") {
Ok(account) => publish_or_unpublish_custom_world_profile(
tx,
&user,
&account,
profile_id.clone(),
author_display_name.clone(),
false,
@@ -441,7 +441,7 @@ pub fn unpublish_custom_world_profile(
fn save_snapshot_impl(
tx: &TxContext,
user: &User,
account: &Account,
saved_at_ms: u64,
game_state_json: String,
bottom_tab: String,
@@ -454,7 +454,7 @@ fn save_snapshot_impl(
let normalized_saved_at_ms = normalize_saved_at_ms(saved_at_ms, tx.timestamp);
let snapshot = SaveSnapshot {
user_id: user.id.clone(),
account_id: account.id.clone(),
version: SAVE_SNAPSHOT_VERSION,
saved_at_ms: normalized_saved_at_ms,
bottom_tab: normalized_bottom_tab,
@@ -463,14 +463,14 @@ fn save_snapshot_impl(
updated_at_ms: timestamp_ms(tx.timestamp),
};
upsert_save_snapshot(tx, snapshot);
sync_profile_dashboard_from_snapshot(tx, user, normalized_saved_at_ms, &game_state_json);
sync_custom_world_profile_from_snapshot(tx, user, normalized_saved_at_ms, &game_state_json);
sync_profile_dashboard_from_snapshot(tx, account, normalized_saved_at_ms, &game_state_json);
sync_custom_world_profile_from_snapshot(tx, account, normalized_saved_at_ms, &game_state_json);
MutationResult::ok("snapshot_saved", "运行时快照已保存")
}
fn upsert_custom_world_profile_impl(
tx: &TxContext,
user: &User,
account: &Account,
profile_id: String,
payload_json: String,
author_display_name: String,
@@ -484,11 +484,11 @@ fn upsert_custom_world_profile_impl(
None => return MutationResult::error("invalid_profile_json", "自定义世界配置 JSON 不合法"),
};
let now_ms = timestamp_ms(tx.timestamp);
let key = custom_world_profile_key(&user.id, &normalized_profile_id);
let key = custom_world_profile_key(&account.id, &normalized_profile_id);
let existing = tx.db.custom_world_profile().id().find(&key);
let row = CustomWorldProfile {
id: key,
user_id: user.id.clone(),
account_id: account.id.clone(),
profile_id: normalized_profile_id,
payload_json,
visibility: existing
@@ -497,7 +497,7 @@ fn upsert_custom_world_profile_impl(
.unwrap_or(CustomWorldPublicationStatus::Draft),
published_at_ms: existing.as_ref().and_then(|row| row.published_at_ms),
updated_at_ms: now_ms,
author_display_name: resolve_author_display_name(&author_display_name, &user.display_name),
author_display_name: resolve_author_display_name(&author_display_name, &account.display_name),
world_name: metadata.world_name,
subtitle: metadata.subtitle,
summary_text: metadata.summary_text,
@@ -513,13 +513,13 @@ fn upsert_custom_world_profile_impl(
fn publish_or_unpublish_custom_world_profile(
tx: &TxContext,
user: &User,
account: &Account,
profile_id: String,
author_display_name: String,
publish: bool,
) -> MutationResult {
let normalized_profile_id = normalize_required_string(&profile_id);
let key = custom_world_profile_key(&user.id, &normalized_profile_id);
let key = custom_world_profile_key(&account.id, &normalized_profile_id);
let Some(existing) = tx.db.custom_world_profile().id().find(&key) else {
return MutationResult::error("custom_world_not_found", "未找到目标自定义世界档案");
};
@@ -536,7 +536,7 @@ fn publish_or_unpublish_custom_world_profile(
},
published_at_ms: if publish { Some(now_ms) } else { None },
updated_at_ms: now_ms,
author_display_name: resolve_author_display_name(&author_display_name, &user.display_name),
author_display_name: resolve_author_display_name(&author_display_name, &account.display_name),
world_name: metadata.world_name,
subtitle: metadata.subtitle,
summary_text: metadata.summary_text,
@@ -555,26 +555,26 @@ fn publish_or_unpublish_custom_world_profile(
}
fn upsert_save_snapshot(tx: &TxContext, row: SaveSnapshot) {
if tx.db.saved_snapshot_row().user_id().find(&row.user_id).is_some() {
tx.db.saved_snapshot_row().user_id().update(row);
if tx.db.saved_snapshot_row().account_id().find(&row.account_id).is_some() {
tx.db.saved_snapshot_row().account_id().update(row);
} else {
tx.db.saved_snapshot_row().insert(row);
}
}
fn upsert_runtime_setting(tx: &TxContext, row: RuntimeSetting) {
if tx.db.runtime_setting().user_id().find(&row.user_id).is_some() {
tx.db.runtime_setting().user_id().update(row);
if tx.db.runtime_setting().account_id().find(&row.account_id).is_some() {
tx.db.runtime_setting().account_id().update(row);
} else {
tx.db.runtime_setting().insert(row);
}
}
fn upsert_browse_history(tx: &TxContext, row: UserBrowseHistory) {
if tx.db.user_browse_history().id().find(&row.id).is_some() {
tx.db.user_browse_history().id().update(row);
fn upsert_browse_history(tx: &TxContext, row: AccountBrowseHistory) {
if tx.db.account_browse_history().id().find(&row.id).is_some() {
tx.db.account_browse_history().id().update(row);
} else {
tx.db.user_browse_history().insert(row);
tx.db.account_browse_history().insert(row);
}
}
@@ -595,8 +595,8 @@ fn upsert_custom_world_session_row(tx: &TxContext, row: CustomWorldSession) {
}
fn upsert_profile_dashboard_state(tx: &TxContext, row: ProfileDashboardState) {
if tx.db.profile_dashboard_state().user_id().find(&row.user_id).is_some() {
tx.db.profile_dashboard_state().user_id().update(row);
if tx.db.profile_dashboard_state().account_id().find(&row.account_id).is_some() {
tx.db.profile_dashboard_state().account_id().update(row);
} else {
tx.db.profile_dashboard_state().insert(row);
}
@@ -618,14 +618,14 @@ fn upsert_profile_wallet_ledger(tx: &TxContext, row: ProfileWalletLedger) {
fn sync_profile_dashboard_from_snapshot(
tx: &TxContext,
user: &User,
account: &Account,
saved_at_ms: u64,
game_state_json: &str,
) {
let Some(game_state) = parse_json(game_state_json) else {
return;
};
let current_state = tx.db.profile_dashboard_state().user_id().find(&user.id);
let current_state = tx.db.profile_dashboard_state().account_id().find(&account.id);
let current_wallet_balance = current_state.as_ref().map(|row| row.wallet_balance).unwrap_or(0);
let current_total_play_time_ms = current_state
.as_ref()
@@ -637,8 +637,8 @@ fn sync_profile_dashboard_from_snapshot(
upsert_profile_wallet_ledger(
tx,
ProfileWalletLedger {
id: profile_wallet_ledger_id(&user.id, &source_key),
user_id: user.id.clone(),
id: profile_wallet_ledger_id(&account.id, &source_key),
account_id: account.id.clone(),
amount_delta: next_wallet_balance - current_wallet_balance,
balance_after: next_wallet_balance,
source_type: "snapshot_sync".to_string(),
@@ -650,7 +650,7 @@ fn sync_profile_dashboard_from_snapshot(
let mut total_play_time_ms = current_total_play_time_ms;
if let Some(world_meta) = resolve_snapshot_world_meta(&game_state) {
let current_world_key = profile_played_world_key(&user.id, &world_meta.world_key);
let current_world_key = profile_played_world_key(&account.id, &world_meta.world_key);
let current_world = tx.db.profile_played_world().id().find(&current_world_key);
let observed_play_time_ms = read_nested_u64(&game_state, &["runtimeStats", "playTimeMs"]);
let incremental_play_time_ms = observed_play_time_ms
@@ -661,9 +661,9 @@ fn sync_profile_dashboard_from_snapshot(
tx,
ProfilePlayedWorld {
id: current_world_key,
user_id: user.id.clone(),
account_id: account.id.clone(),
world_key: world_meta.world_key,
owner_user_id: world_meta.owner_user_id,
owner_account_id: world_meta.owner_account_id,
profile_id: world_meta.profile_id,
world_type: world_meta.world_type,
world_title: world_meta.world_title,
@@ -686,7 +686,7 @@ fn sync_profile_dashboard_from_snapshot(
upsert_profile_dashboard_state(
tx,
ProfileDashboardState {
user_id: user.id.clone(),
account_id: account.id.clone(),
wallet_balance: next_wallet_balance,
total_play_time_ms,
updated_at_ms: saved_at_ms,
@@ -696,7 +696,7 @@ fn sync_profile_dashboard_from_snapshot(
fn sync_custom_world_profile_from_snapshot(
tx: &TxContext,
user: &User,
account: &Account,
saved_at_ms: u64,
game_state_json: &str,
) {
@@ -722,13 +722,13 @@ fn sync_custom_world_profile_from_snapshot(
return;
};
let key = custom_world_profile_key(&user.id, &profile_id);
let key = custom_world_profile_key(&account.id, &profile_id);
let existing = tx.db.custom_world_profile().id().find(&key);
upsert_custom_world_profile_row(
tx,
CustomWorldProfile {
id: key,
user_id: user.id.clone(),
account_id: account.id.clone(),
profile_id,
payload_json,
visibility: existing
@@ -737,7 +737,7 @@ fn sync_custom_world_profile_from_snapshot(
.unwrap_or(CustomWorldPublicationStatus::Draft),
published_at_ms: existing.as_ref().and_then(|row| row.published_at_ms),
updated_at_ms: saved_at_ms,
author_display_name: user.display_name.clone(),
author_display_name: account.display_name.clone(),
world_name: metadata.world_name,
subtitle: metadata.subtitle,
summary_text: metadata.summary_text,
@@ -752,7 +752,7 @@ fn sync_custom_world_profile_from_snapshot(
struct SnapshotWorldMeta {
world_key: String,
owner_user_id: Option<String>,
owner_account_id: Option<String>,
profile_id: Option<String>,
world_type: Option<String>,
world_title: String,
@@ -772,7 +772,7 @@ fn resolve_snapshot_world_meta(game_state: &Value) -> Option<SnapshotWorldMeta>
} else {
format!("custom:{profile_id}")
},
owner_user_id: None,
owner_account_id: None,
profile_id: if profile_id.is_empty() { None } else { Some(profile_id) },
world_type: Some("CUSTOM".to_string()),
world_title,
@@ -797,7 +797,7 @@ fn resolve_snapshot_world_meta(game_state: &Value) -> Option<SnapshotWorldMeta>
.unwrap_or_default();
Some(SnapshotWorldMeta {
world_key: format!("builtin:{world_type}"),
owner_user_id: None,
owner_account_id: None,
profile_id: None,
world_type: Some(world_type),
world_title,
@@ -962,7 +962,7 @@ fn to_profile_wallet_ledger_view(row: ProfileWalletLedger) -> ProfileWalletLedge
fn to_profile_played_world_view(row: ProfilePlayedWorld) -> ProfilePlayedWorldView {
ProfilePlayedWorldView {
world_key: row.world_key,
owner_user_id: row.owner_user_id,
owner_account_id: row.owner_account_id,
profile_id: row.profile_id,
world_type: row.world_type,
world_title: row.world_title,
@@ -973,9 +973,9 @@ fn to_profile_played_world_view(row: ProfilePlayedWorld) -> ProfilePlayedWorldVi
}
}
fn to_platform_browse_history_view(row: UserBrowseHistory) -> PlatformBrowseHistoryView {
fn to_platform_browse_history_view(row: AccountBrowseHistory) -> PlatformBrowseHistoryView {
PlatformBrowseHistoryView {
owner_user_id: row.owner_user_id,
owner_account_id: row.owner_account_id,
profile_id: row.profile_id,
world_name: row.world_name,
subtitle: row.subtitle,
@@ -989,7 +989,7 @@ fn to_platform_browse_history_view(row: UserBrowseHistory) -> PlatformBrowseHist
fn to_custom_world_profile_view(row: CustomWorldProfile) -> CustomWorldProfileView {
CustomWorldProfileView {
owner_user_id: row.user_id,
owner_account_id: row.account_id,
profile_id: row.profile_id,
payload_json: row.payload_json,
visibility: row.visibility,
@@ -1008,7 +1008,7 @@ fn to_custom_world_profile_view(row: CustomWorldProfile) -> CustomWorldProfileVi
fn to_custom_world_gallery_card_view(row: CustomWorldProfile) -> CustomWorldGalleryCardView {
CustomWorldGalleryCardView {
owner_user_id: row.user_id,
owner_account_id: row.account_id,
profile_id: row.profile_id,
visibility: row.visibility,
published_at_ms: row.published_at_ms,

View File

@@ -67,7 +67,7 @@ pub struct RequestMeta {
#[derive(SpacetimeType, Clone, Debug)]
pub struct PlatformBrowseHistoryWriteInput {
pub owner_user_id: String,
pub owner_account_id: String,
pub profile_id: String,
pub world_name: String,
pub subtitle: String,
@@ -199,7 +199,7 @@ pub struct ClientAppConfigView {
#[derive(SpacetimeType, Clone, Debug)]
pub struct AuthStateView {
pub user_id: String,
pub account_id: String,
pub identity: Identity,
pub display_name: String,
pub phone_number_masked: Option<String>,
@@ -244,7 +244,7 @@ pub struct ProfileWalletLedgerView {
#[derive(SpacetimeType, Clone, Debug)]
pub struct ProfilePlayedWorldView {
pub world_key: String,
pub owner_user_id: Option<String>,
pub owner_account_id: Option<String>,
pub profile_id: Option<String>,
pub world_type: Option<String>,
pub world_title: String,
@@ -256,7 +256,7 @@ pub struct ProfilePlayedWorldView {
#[derive(SpacetimeType, Clone, Debug)]
pub struct PlatformBrowseHistoryView {
pub owner_user_id: String,
pub owner_account_id: String,
pub profile_id: String,
pub world_name: String,
pub subtitle: String,
@@ -269,7 +269,7 @@ pub struct PlatformBrowseHistoryView {
#[derive(SpacetimeType, Clone, Debug)]
pub struct CustomWorldProfileView {
pub owner_user_id: String,
pub owner_account_id: String,
pub profile_id: String,
pub payload_json: String,
pub visibility: CustomWorldPublicationStatus,
@@ -287,7 +287,7 @@ pub struct CustomWorldProfileView {
#[derive(SpacetimeType, Clone, Debug)]
pub struct CustomWorldGalleryCardView {
pub owner_user_id: String,
pub owner_account_id: String,
pub profile_id: String,
pub visibility: CustomWorldPublicationStatus,
pub published_at_ms: Option<u64>,
@@ -304,7 +304,7 @@ pub struct CustomWorldGalleryCardView {
#[derive(SpacetimeType, Clone, Debug)]
pub struct PublishedCustomWorldProfileView {
pub owner_user_id: String,
pub owner_account_id: String,
pub profile_id: String,
pub payload_json: String,
pub visibility: CustomWorldPublicationStatus,
@@ -413,9 +413,9 @@ pub struct AppConfig {
pub updated_at_ms: u64,
}
#[table(accessor = user)]
#[table(accessor = account)]
#[derive(Clone, Debug)]
pub struct User {
pub struct Account {
#[primary_key]
pub id: String,
#[unique]
@@ -432,13 +432,13 @@ pub struct User {
pub updated_at_ms: u64,
}
#[table(accessor = auth_identity)]
#[table(accessor = account_identity)]
#[derive(Clone, Debug)]
pub struct AuthIdentity {
pub struct AccountIdentity {
#[primary_key]
pub id: String,
#[index(btree)]
pub user_id: String,
pub account_id: String,
pub provider: AuthIdentityProvider,
pub provider_uid: String,
pub provider_union_id: Option<String>,
@@ -450,13 +450,13 @@ pub struct AuthIdentity {
pub updated_at_ms: u64,
}
#[table(accessor = user_session)]
#[table(accessor = account_session)]
#[derive(Clone, Debug)]
pub struct UserSession {
pub struct AccountSession {
#[primary_key]
pub id: String,
#[index(btree)]
pub user_id: String,
pub account_id: String,
pub refresh_token_hash: String,
pub client_type: String,
pub user_agent: Option<String>,
@@ -475,7 +475,7 @@ pub struct AuthAuditLog {
#[auto_inc]
pub id: u64,
#[index(btree)]
pub user_id: String,
pub account_id: String,
pub event_type: String,
pub detail: String,
pub ip: Option<String>,
@@ -524,7 +524,7 @@ pub struct AuthRiskBlock {
#[derive(Clone, Debug)]
pub struct SaveSnapshot {
#[primary_key]
pub user_id: String,
pub account_id: String,
pub version: u32,
pub saved_at_ms: u64,
pub bottom_tab: String,
@@ -537,7 +537,7 @@ pub struct SaveSnapshot {
#[derive(Clone, Debug)]
pub struct RuntimeSetting {
#[primary_key]
pub user_id: String,
pub account_id: String,
pub music_volume: f32,
pub updated_at_ms: u64,
}
@@ -548,7 +548,7 @@ pub struct CustomWorldProfile {
#[primary_key]
pub id: String,
#[index(btree)]
pub user_id: String,
pub account_id: String,
pub profile_id: String,
#[index(btree)]
pub visibility: CustomWorldPublicationStatus,
@@ -572,7 +572,7 @@ pub struct CustomWorldSession {
#[primary_key]
pub id: String,
#[index(btree)]
pub user_id: String,
pub account_id: String,
pub session_id: String,
pub payload_json: String,
pub created_at_ms: u64,
@@ -583,7 +583,7 @@ pub struct CustomWorldSession {
#[derive(Clone, Debug)]
pub struct ProfileDashboardState {
#[primary_key]
pub user_id: String,
pub account_id: String,
pub wallet_balance: i64,
pub total_play_time_ms: u64,
pub updated_at_ms: u64,
@@ -595,7 +595,7 @@ pub struct ProfileWalletLedger {
#[primary_key]
pub id: String,
#[index(btree)]
pub user_id: String,
pub account_id: String,
pub amount_delta: i64,
pub balance_after: i64,
pub source_type: String,
@@ -609,9 +609,9 @@ pub struct ProfilePlayedWorld {
#[primary_key]
pub id: String,
#[index(btree)]
pub user_id: String,
pub account_id: String,
pub world_key: String,
pub owner_user_id: Option<String>,
pub owner_account_id: Option<String>,
pub profile_id: Option<String>,
pub world_type: Option<String>,
pub world_title: String,
@@ -621,14 +621,14 @@ pub struct ProfilePlayedWorld {
pub last_observed_play_time_ms: u64,
}
#[table(accessor = user_browse_history)]
#[table(accessor = account_browse_history)]
#[derive(Clone, Debug)]
pub struct UserBrowseHistory {
pub struct AccountBrowseHistory {
#[primary_key]
pub id: String,
#[index(btree)]
pub user_id: String,
pub owner_user_id: String,
pub account_id: String,
pub owner_account_id: String,
pub profile_id: String,
pub world_name: String,
pub subtitle: String,