账户系统完善,使用uuid+多identity记录
This commit is contained in:
@@ -36,6 +36,32 @@
|
||||
- 有 JWT 时使用 SpacetimeDB 自带 `sender_auth().jwt()` 建档,`login_provider` 标记为 `jwt`
|
||||
- 用户主键按 `user_<identity_hex>` 生成,避免再依赖原先 Node 自签 access token / refresh token 流程
|
||||
|
||||
### 2.2 内部账户模型状态
|
||||
|
||||
当前 STDB 私有实现已经开始显式转向账户语义:
|
||||
|
||||
- 私有 Rust struct 已经切到 `Account / AccountIdentity / AccountSession`
|
||||
- `auth.rs / runtime.rs / lib.rs` 内部 helper 和生命周期接线也开始改用 `account` 语义命名
|
||||
- 公开 view / procedure 名称暂时保持不变,但当前 schema 字段已经切到 `account_id / owner_account_id`
|
||||
- 前端 TypeScript bindings 已经重新生成并同步适配了 `account_id` 语义
|
||||
|
||||
### 2.1 当前统一账户策略
|
||||
|
||||
当前已经开始按“统一账户,多设备会话”方向调整:
|
||||
|
||||
- 新建账户时,账户主键已经不再直接使用连接 identity,而是生成独立 `acct_*` 账户 id
|
||||
- 当前设备在短信验证时,如果手机号已命中已有用户,不再直接报“手机号已绑定其他账号”
|
||||
- 当前连接的 identity / session 会被归并到这个已有手机号用户
|
||||
- 当前游客账户下的快照、设置、自定义世界、游玩统计、浏览历史等运行时数据也会一起迁移到目标账户
|
||||
- 这样同一个手机号可以在多个设备上同时建立会话,并归到同一个用户主体下
|
||||
|
||||
当前限制:
|
||||
|
||||
- 归并的是“当前连接身份”和“当前会话”
|
||||
- 当前的账户数据迁移是规则式合并,不是全量业务语义级合并
|
||||
- 例如看板/游玩统计用了保守合并策略,自定义世界同名冲突按更新时间取新
|
||||
- 也就是说,统一账户主语义已经开始生效,但后续仍值得补一轮更细的并档策略
|
||||
|
||||
### 3. 短信验证门禁
|
||||
|
||||
当前行为已经按你的要求落地:
|
||||
@@ -62,7 +88,7 @@
|
||||
- `client_app_config`
|
||||
- `my_auth_state`
|
||||
- `my_auth_audit_logs`
|
||||
- `my_user_sessions`
|
||||
- `my_account_sessions`
|
||||
- `my_auth_risk_blocks`
|
||||
- `my_snapshot`
|
||||
- `my_runtime_settings`
|
||||
@@ -85,7 +111,7 @@
|
||||
|
||||
为了承接客户端账户弹窗,本轮补了:
|
||||
|
||||
- `my_user_sessions`
|
||||
- `my_account_sessions`
|
||||
- 用于读取当前账号关联的会话列表
|
||||
- `my_auth_risk_blocks`
|
||||
- 用于读取当前账号手机号/IP 对应的保护记录
|
||||
@@ -153,7 +179,7 @@ spacetime sql genarrative-local "$(tr '\n' ' ' < scripts/spacetime/init_local_ap
|
||||
- `src/services/storageService.ts`
|
||||
- 已从 `/api/runtime/*` 的存档/设置/资料库接口切到 Spacetime。
|
||||
- `src/services/authService.ts`
|
||||
- 现在也会读取 `my_user_sessions` / `my_auth_risk_blocks`,并调用 `lift_my_risk_block`。
|
||||
- 现在也会读取 `my_account_sessions` / `my_auth_risk_blocks`,并调用 `lift_my_risk_block`。
|
||||
- `src/components/auth/AuthGate.tsx`
|
||||
- 已改成默认游客建连,并监听 `verification_prompt_event` / `kick_event`。
|
||||
- `src/components/auth/PhoneVerificationModal.tsx`
|
||||
|
||||
@@ -57,7 +57,7 @@ export type ProfileWalletLedgerResponse = {
|
||||
|
||||
export type ProfilePlayedWorkSummary = {
|
||||
worldKey: string;
|
||||
ownerUserId: string | null;
|
||||
ownerAccountId: string | null;
|
||||
profileId: string | null;
|
||||
worldType: string | null;
|
||||
worldTitle: string;
|
||||
@@ -87,7 +87,7 @@ export type CustomWorldProfileRecord = JsonObject & {
|
||||
};
|
||||
|
||||
export type CustomWorldLibraryEntry<TProfile = CustomWorldProfileRecord> = {
|
||||
ownerUserId: string;
|
||||
ownerAccountId: string;
|
||||
profileId: string;
|
||||
profile: TProfile;
|
||||
visibility: CustomWorldPublicationStatus;
|
||||
@@ -130,7 +130,7 @@ export type CustomWorldGalleryDetailResponse<
|
||||
};
|
||||
|
||||
export type PlatformBrowseHistoryEntry = {
|
||||
ownerUserId: string;
|
||||
ownerAccountId: string;
|
||||
profileId: string;
|
||||
worldName: string;
|
||||
subtitle: string;
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
} from '../ai/chatPromptBuilders.js';
|
||||
import { generateNextStoryFromOrchestrator } from '../ai/storyOrchestrator.js';
|
||||
import { resolveCombatAction } from '../combat/combatResolutionService.js';
|
||||
import { isSupportedInventoryStoryFunctionId,resolveInventoryStoryAction } from '../inventory/inventoryStoryActionService.js';
|
||||
import { isSupportedInventoryStoryFunctionId,resolveInventoryStoryAction } from '../inventory/inventory
|
||||
.js';
|
||||
import {
|
||||
ensureNpcInventorySessionState,
|
||||
isSupportedNpcInventoryStoryFunctionId,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
"账号尚未完成短信验证,请先验证手机号",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¤t_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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -509,7 +509,7 @@ export function PlatformHomeView({
|
||||
<div className="flex gap-3 overflow-x-auto pb-1 scrollbar-hide">
|
||||
{featuredShelf.map((entry: CustomWorldGalleryCard) => (
|
||||
<WorldCard
|
||||
key={`${entry.ownerUserId}:${entry.profileId}:featured`}
|
||||
key={`${entry.ownerAccountId}:${entry.profileId}:featured`}
|
||||
entry={entry}
|
||||
badge="推荐"
|
||||
metaLabel={describePlatformThemeLabel(entry.themeMode)}
|
||||
@@ -530,7 +530,7 @@ export function PlatformHomeView({
|
||||
<div className="flex gap-3 overflow-x-auto pb-1 scrollbar-hide">
|
||||
{latestEntries.map((entry: CustomWorldGalleryCard) => (
|
||||
<WorldCard
|
||||
key={`${entry.ownerUserId}:${entry.profileId}:latest`}
|
||||
key={`${entry.ownerAccountId}:${entry.profileId}:latest`}
|
||||
entry={entry}
|
||||
badge={formatPlatformWorldTime(entry.publishedAt)}
|
||||
metaLabel={entry.authorDisplayName}
|
||||
@@ -580,7 +580,7 @@ export function PlatformHomeView({
|
||||
{myEntries.map(
|
||||
(entry: CustomWorldLibraryEntry<CustomWorldProfile>) => (
|
||||
<WorldCard
|
||||
key={`${entry.ownerUserId}:${entry.profileId}:mine`}
|
||||
key={`${entry.ownerAccountId}:${entry.profileId}:mine`}
|
||||
entry={entry}
|
||||
badge={entry.visibility === 'published' ? '已发布' : '草稿'}
|
||||
metaLabel={
|
||||
@@ -820,11 +820,11 @@ export function PlatformHomeView({
|
||||
<div className="flex gap-3 overflow-x-auto pb-1 scrollbar-hide">
|
||||
{historyEntries.map((entry) => (
|
||||
<button
|
||||
key={`${entry.ownerUserId}:${entry.profileId}:history`}
|
||||
key={`${entry.ownerAccountId}:${entry.profileId}:history`}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onOpenGalleryDetail({
|
||||
ownerUserId: entry.ownerUserId,
|
||||
ownerAccountId: entry.ownerAccountId,
|
||||
profileId: entry.profileId,
|
||||
visibility: 'published',
|
||||
publishedAt: entry.visitedAt,
|
||||
|
||||
@@ -233,7 +233,7 @@ beforeEach(() => {
|
||||
vi.mocked(deleteCustomWorldProfile).mockResolvedValue([]);
|
||||
vi.mocked(upsertCustomWorldProfile).mockResolvedValue({
|
||||
entry: {
|
||||
ownerUserId: 'user-1',
|
||||
ownerAccountId: 'user-1',
|
||||
profileId: 'agent-draft-custom-world-agent-session-1',
|
||||
profile: {
|
||||
id: 'agent-draft-custom-world-agent-session-1',
|
||||
@@ -477,7 +477,7 @@ test('profile tab loads server browse history and can clear it after confirmatio
|
||||
|
||||
vi.mocked(listProfileBrowseHistory).mockResolvedValue([
|
||||
{
|
||||
ownerUserId: 'author-1',
|
||||
ownerAccountId: 'author-1',
|
||||
profileId: 'world-1',
|
||||
worldName: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
@@ -514,7 +514,7 @@ test('owned world detail can delete a work and return to the create tab list', a
|
||||
|
||||
vi.mocked(listCustomWorldLibrary).mockResolvedValue([
|
||||
{
|
||||
ownerUserId: 'user-1',
|
||||
ownerAccountId: 'user-1',
|
||||
profileId: 'world-delete-1',
|
||||
profile: {
|
||||
id: 'world-delete-1',
|
||||
|
||||
@@ -326,7 +326,7 @@ export function PreGameSelectionFlow({
|
||||
|
||||
const nextOwnedEntry = savedCustomWorldEntries.find(
|
||||
(entry) =>
|
||||
entry.ownerUserId === selectedDetailEntry.ownerUserId &&
|
||||
entry.ownerAccountId === selectedDetailEntry.ownerAccountId &&
|
||||
entry.profileId === selectedDetailEntry.profileId,
|
||||
);
|
||||
if (nextOwnedEntry && nextOwnedEntry !== selectedDetailEntry) {
|
||||
@@ -934,7 +934,7 @@ export function PreGameSelectionFlow({
|
||||
) => {
|
||||
if (entry.visibility === 'published') {
|
||||
void appendBrowseHistoryEntry({
|
||||
ownerUserId: entry.ownerUserId,
|
||||
ownerAccountId: entry.ownerAccountId,
|
||||
profileId: entry.profileId,
|
||||
worldName: entry.worldName,
|
||||
subtitle: entry.subtitle,
|
||||
@@ -955,12 +955,12 @@ export function PreGameSelectionFlow({
|
||||
setDetailError(null);
|
||||
try {
|
||||
const detailEntry = await getCustomWorldGalleryDetail(
|
||||
entry.ownerUserId,
|
||||
entry.ownerAccountId,
|
||||
entry.profileId,
|
||||
);
|
||||
setSelectedDetailEntry(detailEntry);
|
||||
void appendBrowseHistoryEntry({
|
||||
ownerUserId: detailEntry.ownerUserId,
|
||||
ownerAccountId: detailEntry.ownerAccountId,
|
||||
profileId: detailEntry.profileId,
|
||||
worldName: detailEntry.worldName,
|
||||
subtitle: detailEntry.subtitle,
|
||||
@@ -1186,7 +1186,7 @@ export function PreGameSelectionFlow({
|
||||
selectedDetailEntry &&
|
||||
savedCustomWorldEntries.some(
|
||||
(entry) =>
|
||||
entry.ownerUserId === selectedDetailEntry.ownerUserId &&
|
||||
entry.ownerAccountId === selectedDetailEntry.ownerAccountId &&
|
||||
entry.profileId === selectedDetailEntry.profileId,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ function createMemoryStorage() {
|
||||
|
||||
function createAuthStateRow(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
userId: 'user_1',
|
||||
accountId: 'acct_1',
|
||||
identity: {
|
||||
isEqual: vi.fn(() => true),
|
||||
toHexString: vi.fn(() => 'abc'),
|
||||
@@ -125,7 +125,7 @@ function createConnection(options: {
|
||||
my_auth_audit_logs: {
|
||||
iter: vi.fn(() => []),
|
||||
},
|
||||
my_user_sessions: {
|
||||
my_account_sessions: {
|
||||
iter: vi.fn(() =>
|
||||
options.sessionRows ?? [
|
||||
{
|
||||
|
||||
@@ -354,7 +354,7 @@ export async function getCurrentAuthUser(): Promise<AuthSessionSnapshot> {
|
||||
export async function getAuthSessions() {
|
||||
const connection = await ensureSpacetimeConnection();
|
||||
const currentSessionId = getCurrentSpacetimeSessionId(connection);
|
||||
return Array.from(connection.db.my_user_sessions.iter()).map((row) =>
|
||||
return Array.from(connection.db.my_account_sessions.iter()).map((row) =>
|
||||
mapAuthSession(row, { currentSessionId }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,17 +41,17 @@ function normalizeHistoryEntry(
|
||||
return null;
|
||||
}
|
||||
|
||||
const ownerUserId = readString(value.ownerUserId);
|
||||
const ownerAccountId = readString(value.ownerAccountId);
|
||||
const profileId = readString(value.profileId);
|
||||
const worldName = readString(value.worldName);
|
||||
const visitedAt = readString(value.visitedAt);
|
||||
|
||||
if (!ownerUserId || !profileId || !worldName || !visitedAt) {
|
||||
if (!ownerAccountId || !profileId || !worldName || !visitedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
ownerUserId,
|
||||
ownerAccountId,
|
||||
profileId,
|
||||
worldName,
|
||||
subtitle: readString(value.subtitle),
|
||||
@@ -109,7 +109,7 @@ export function writePlatformBrowseHistory(
|
||||
}
|
||||
|
||||
const nextEntry: PlatformBrowseHistoryEntry = {
|
||||
ownerUserId: entry.ownerUserId.trim(),
|
||||
ownerAccountId: entry.ownerAccountId.trim(),
|
||||
profileId: entry.profileId.trim(),
|
||||
worldName: entry.worldName.trim(),
|
||||
subtitle: entry.subtitle?.trim() || '',
|
||||
@@ -122,7 +122,7 @@ export function writePlatformBrowseHistory(
|
||||
const deduped = readPlatformBrowseHistory(user).filter(
|
||||
(current) =>
|
||||
!(
|
||||
current.ownerUserId === nextEntry.ownerUserId &&
|
||||
current.ownerAccountId === nextEntry.ownerAccountId &&
|
||||
current.profileId === nextEntry.profileId
|
||||
),
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ function createConnection() {
|
||||
my_browse_history: {
|
||||
iter: vi.fn(() => [
|
||||
{
|
||||
ownerUserId: 'author-1',
|
||||
ownerAccountId: 'author-1',
|
||||
profileId: 'profile-1',
|
||||
worldName: '测试世界',
|
||||
subtitle: '测试副标题',
|
||||
@@ -82,7 +82,7 @@ describe('storageService with SpacetimeDB', () => {
|
||||
spacetimeMocks.ensureSpacetimeConnection.mockResolvedValue(connection);
|
||||
|
||||
await upsertProfileBrowseHistory({
|
||||
ownerUserId: 'user-1',
|
||||
ownerAccountId: 'user-1',
|
||||
profileId: 'profile-1',
|
||||
worldName: '测试世界',
|
||||
subtitle: '测试副标题',
|
||||
@@ -96,7 +96,7 @@ describe('storageService with SpacetimeDB', () => {
|
||||
expect.objectContaining({
|
||||
entries: [
|
||||
expect.objectContaining({
|
||||
ownerUserId: 'user-1',
|
||||
ownerAccountId: 'user-1',
|
||||
profileId: 'profile-1',
|
||||
worldName: '测试世界',
|
||||
}),
|
||||
@@ -111,7 +111,7 @@ describe('storageService with SpacetimeDB', () => {
|
||||
|
||||
await syncProfileBrowseHistory([
|
||||
{
|
||||
ownerUserId: 'user-1',
|
||||
ownerAccountId: 'user-1',
|
||||
profileId: 'profile-1',
|
||||
worldName: '测试世界',
|
||||
subtitle: '测试副标题',
|
||||
|
||||
@@ -240,7 +240,7 @@ export async function upsertCustomWorldProfile(
|
||||
const entry =
|
||||
entries.find((item) => item.profileId === profile.id) ??
|
||||
mapCustomWorldLibraryEntry({
|
||||
ownerUserId: '',
|
||||
ownerAccountId: '',
|
||||
profileId: profile.id,
|
||||
payloadJson: JSON.stringify(profile),
|
||||
visibility: { tag: 'Draft' },
|
||||
@@ -343,7 +343,7 @@ export async function listCustomWorldGallery(
|
||||
}
|
||||
|
||||
export async function getCustomWorldGalleryDetail(
|
||||
ownerUserId: string,
|
||||
ownerAccountId: string,
|
||||
profileId: string,
|
||||
_options: RuntimeRequestOptions = {},
|
||||
) {
|
||||
@@ -352,7 +352,7 @@ export async function getCustomWorldGalleryDetail(
|
||||
.map(mapPublishedProfile)
|
||||
.find(
|
||||
(row) =>
|
||||
row.ownerUserId === ownerUserId && row.profileId === profileId,
|
||||
row.ownerAccountId === ownerAccountId && row.profileId === profileId,
|
||||
);
|
||||
|
||||
if (!entry) {
|
||||
@@ -380,7 +380,7 @@ export async function upsertProfileBrowseHistory(
|
||||
meta: buildRequestMeta(),
|
||||
entries: [
|
||||
{
|
||||
ownerUserId: entry.ownerUserId,
|
||||
ownerAccountId: entry.ownerAccountId,
|
||||
profileId: entry.profileId,
|
||||
worldName: entry.worldName,
|
||||
subtitle: entry.subtitle,
|
||||
@@ -408,7 +408,7 @@ export async function syncProfileBrowseHistory(
|
||||
const result = await connection.procedures.upsertPlatformBrowseHistory({
|
||||
meta: buildRequestMeta(),
|
||||
entries: entries.map((entry) => ({
|
||||
ownerUserId: entry.ownerUserId,
|
||||
ownerAccountId: entry.ownerAccountId,
|
||||
profileId: entry.profileId,
|
||||
worldName: entry.worldName,
|
||||
subtitle: entry.subtitle,
|
||||
|
||||
@@ -55,6 +55,7 @@ import * as VerifySmsCodeProcedure from "./verify_sms_code_procedure";
|
||||
// Import all table schema definitions
|
||||
import ClientAppConfigRow from "./client_app_config_table";
|
||||
import KickEventRow from "./kick_event_table";
|
||||
import MyAccountSessionsRow from "./my_account_sessions_table";
|
||||
import MyAuthAuditLogsRow from "./my_auth_audit_logs_table";
|
||||
import MyAuthRiskBlocksRow from "./my_auth_risk_blocks_table";
|
||||
import MyAuthStateRow from "./my_auth_state_table";
|
||||
@@ -66,7 +67,6 @@ import MyProfilePlayedWorldsRow from "./my_profile_played_worlds_table";
|
||||
import MyProfileWalletLedgerRow from "./my_profile_wallet_ledger_table";
|
||||
import MyRuntimeSettingsRow from "./my_runtime_settings_table";
|
||||
import MySnapshotRow from "./my_snapshot_table";
|
||||
import MyUserSessionsRow from "./my_user_sessions_table";
|
||||
import PublishedCustomWorldGalleryRow from "./published_custom_world_gallery_table";
|
||||
import PublishedCustomWorldProfilesRow from "./published_custom_world_profiles_table";
|
||||
import SessionRevocationEventRow from "./session_revocation_event_table";
|
||||
@@ -107,6 +107,13 @@ const tablesSchema = __schema({
|
||||
constraints: [
|
||||
],
|
||||
}, ClientAppConfigRow),
|
||||
my_account_sessions: __table({
|
||||
name: 'my_account_sessions',
|
||||
indexes: [
|
||||
],
|
||||
constraints: [
|
||||
],
|
||||
}, MyAccountSessionsRow),
|
||||
my_auth_audit_logs: __table({
|
||||
name: 'my_auth_audit_logs',
|
||||
indexes: [
|
||||
@@ -184,13 +191,6 @@ const tablesSchema = __schema({
|
||||
constraints: [
|
||||
],
|
||||
}, MySnapshotRow),
|
||||
my_user_sessions: __table({
|
||||
name: 'my_user_sessions',
|
||||
indexes: [
|
||||
],
|
||||
constraints: [
|
||||
],
|
||||
}, MyUserSessionsRow),
|
||||
published_custom_world_gallery: __table({
|
||||
name: 'published_custom_world_gallery',
|
||||
indexes: [
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
|
||||
|
||||
export default __t.row({
|
||||
userId: __t.string().name("user_id"),
|
||||
accountId: __t.string().name("account_id"),
|
||||
identity: __t.identity(),
|
||||
displayName: __t.string().name("display_name"),
|
||||
phoneNumberMasked: __t.option(__t.string()).name("phone_number_masked"),
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
|
||||
|
||||
export default __t.row({
|
||||
ownerUserId: __t.string().name("owner_user_id"),
|
||||
ownerAccountId: __t.string().name("owner_account_id"),
|
||||
profileId: __t.string().name("profile_id"),
|
||||
worldName: __t.string().name("world_name"),
|
||||
subtitle: __t.string(),
|
||||
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
type Infer as __Infer,
|
||||
} from "spacetimedb";
|
||||
import {
|
||||
CustomWorldPublicationStatus,
|
||||
CustomWorldThemeMode,
|
||||
CustomWorldPublicationStatus,
|
||||
} from "./types";
|
||||
|
||||
|
||||
export default __t.row({
|
||||
ownerUserId: __t.string().name("owner_user_id"),
|
||||
ownerAccountId: __t.string().name("owner_account_id"),
|
||||
profileId: __t.string().name("profile_id"),
|
||||
payloadJson: __t.string().name("payload_json"),
|
||||
get visibility() {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
|
||||
export default __t.row({
|
||||
worldKey: __t.string().name("world_key"),
|
||||
ownerUserId: __t.option(__t.string()).name("owner_user_id"),
|
||||
ownerAccountId: __t.option(__t.string()).name("owner_account_id"),
|
||||
profileId: __t.option(__t.string()).name("profile_id"),
|
||||
worldType: __t.option(__t.string()).name("world_type"),
|
||||
worldTitle: __t.string().name("world_title"),
|
||||
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
type Infer as __Infer,
|
||||
} from "spacetimedb";
|
||||
import {
|
||||
CustomWorldPublicationStatus,
|
||||
CustomWorldThemeMode,
|
||||
CustomWorldPublicationStatus,
|
||||
} from "./types";
|
||||
|
||||
|
||||
export default __t.row({
|
||||
ownerUserId: __t.string().name("owner_user_id"),
|
||||
ownerAccountId: __t.string().name("owner_account_id"),
|
||||
profileId: __t.string().name("profile_id"),
|
||||
get visibility() {
|
||||
return CustomWorldPublicationStatus;
|
||||
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
type Infer as __Infer,
|
||||
} from "spacetimedb";
|
||||
import {
|
||||
CustomWorldPublicationStatus,
|
||||
CustomWorldThemeMode,
|
||||
CustomWorldPublicationStatus,
|
||||
} from "./types";
|
||||
|
||||
|
||||
export default __t.row({
|
||||
ownerUserId: __t.string().name("owner_user_id"),
|
||||
ownerAccountId: __t.string().name("owner_account_id"),
|
||||
profileId: __t.string().name("profile_id"),
|
||||
payloadJson: __t.string().name("payload_json"),
|
||||
get visibility() {
|
||||
|
||||
@@ -10,6 +10,75 @@ import {
|
||||
type Infer as __Infer,
|
||||
} from "spacetimedb";
|
||||
|
||||
export const Account = __t.object("Account", {
|
||||
id: __t.string(),
|
||||
identity: __t.identity(),
|
||||
username: __t.option(__t.string()),
|
||||
passwordHash: __t.option(__t.string()),
|
||||
tokenVersion: __t.u32(),
|
||||
displayName: __t.string(),
|
||||
get loginProvider() {
|
||||
return LoginProvider;
|
||||
},
|
||||
get accountStatus() {
|
||||
return AccountStatus;
|
||||
},
|
||||
phoneNumber: __t.option(__t.string()),
|
||||
phoneVerifiedAtMs: __t.option(__t.u64()),
|
||||
createdAtMs: __t.u64(),
|
||||
updatedAtMs: __t.u64(),
|
||||
});
|
||||
export type Account = __Infer<typeof Account>;
|
||||
|
||||
export const AccountBrowseHistory = __t.object("AccountBrowseHistory", {
|
||||
id: __t.string(),
|
||||
accountId: __t.string(),
|
||||
ownerAccountId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
worldName: __t.string(),
|
||||
subtitle: __t.string(),
|
||||
summaryText: __t.string(),
|
||||
coverImageSrc: __t.option(__t.string()),
|
||||
get themeMode() {
|
||||
return CustomWorldThemeMode;
|
||||
},
|
||||
authorDisplayName: __t.string(),
|
||||
visitedAtMs: __t.u64(),
|
||||
});
|
||||
export type AccountBrowseHistory = __Infer<typeof AccountBrowseHistory>;
|
||||
|
||||
export const AccountIdentity = __t.object("AccountIdentity", {
|
||||
id: __t.string(),
|
||||
accountId: __t.string(),
|
||||
get provider() {
|
||||
return AuthIdentityProvider;
|
||||
},
|
||||
providerUid: __t.string(),
|
||||
providerUnionId: __t.option(__t.string()),
|
||||
displayName: __t.option(__t.string()),
|
||||
avatarUrl: __t.option(__t.string()),
|
||||
isVerified: __t.bool(),
|
||||
metaJson: __t.option(__t.string()),
|
||||
createdAtMs: __t.u64(),
|
||||
updatedAtMs: __t.u64(),
|
||||
});
|
||||
export type AccountIdentity = __Infer<typeof AccountIdentity>;
|
||||
|
||||
export const AccountSession = __t.object("AccountSession", {
|
||||
id: __t.string(),
|
||||
accountId: __t.string(),
|
||||
refreshTokenHash: __t.string(),
|
||||
clientType: __t.string(),
|
||||
userAgent: __t.option(__t.string()),
|
||||
ip: __t.option(__t.string()),
|
||||
expiresAtMs: __t.option(__t.u64()),
|
||||
revokedAtMs: __t.option(__t.u64()),
|
||||
createdAtMs: __t.u64(),
|
||||
updatedAtMs: __t.u64(),
|
||||
lastSeenAtMs: __t.u64(),
|
||||
});
|
||||
export type AccountSession = __Infer<typeof AccountSession>;
|
||||
|
||||
// The tagged union or sum type for the algebraic type `AccountStatus`.
|
||||
export const AccountStatus = __t.enum("AccountStatus", {
|
||||
Active: __t.unit(),
|
||||
@@ -73,7 +142,7 @@ export type AppConfig = __Infer<typeof AppConfig>;
|
||||
|
||||
export const AuthAuditLog = __t.object("AuthAuditLog", {
|
||||
id: __t.u64(),
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
eventType: __t.string(),
|
||||
detail: __t.string(),
|
||||
ip: __t.option(__t.string()),
|
||||
@@ -93,23 +162,6 @@ export const AuthAuditLogView = __t.object("AuthAuditLogView", {
|
||||
});
|
||||
export type AuthAuditLogView = __Infer<typeof AuthAuditLogView>;
|
||||
|
||||
export const AuthIdentity = __t.object("AuthIdentity", {
|
||||
id: __t.string(),
|
||||
userId: __t.string(),
|
||||
get provider() {
|
||||
return AuthIdentityProvider;
|
||||
},
|
||||
providerUid: __t.string(),
|
||||
providerUnionId: __t.option(__t.string()),
|
||||
displayName: __t.option(__t.string()),
|
||||
avatarUrl: __t.option(__t.string()),
|
||||
isVerified: __t.bool(),
|
||||
metaJson: __t.option(__t.string()),
|
||||
createdAtMs: __t.u64(),
|
||||
updatedAtMs: __t.u64(),
|
||||
});
|
||||
export type AuthIdentity = __Infer<typeof AuthIdentity>;
|
||||
|
||||
// The tagged union or sum type for the algebraic type `AuthIdentityProvider`.
|
||||
export const AuthIdentityProvider = __t.enum("AuthIdentityProvider", {
|
||||
Guest: __t.unit(),
|
||||
@@ -156,7 +208,7 @@ export const AuthSessionView = __t.object("AuthSessionView", {
|
||||
export type AuthSessionView = __Infer<typeof AuthSessionView>;
|
||||
|
||||
export const AuthStateView = __t.object("AuthStateView", {
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
identity: __t.identity(),
|
||||
displayName: __t.string(),
|
||||
phoneNumberMasked: __t.option(__t.string()),
|
||||
@@ -187,7 +239,7 @@ export const ClientAppConfigView = __t.object("ClientAppConfigView", {
|
||||
export type ClientAppConfigView = __Infer<typeof ClientAppConfigView>;
|
||||
|
||||
export const CustomWorldGalleryCardView = __t.object("CustomWorldGalleryCardView", {
|
||||
ownerUserId: __t.string(),
|
||||
ownerAccountId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
get visibility() {
|
||||
return CustomWorldPublicationStatus;
|
||||
@@ -209,7 +261,7 @@ export type CustomWorldGalleryCardView = __Infer<typeof CustomWorldGalleryCardVi
|
||||
|
||||
export const CustomWorldProfile = __t.object("CustomWorldProfile", {
|
||||
id: __t.string(),
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
get visibility() {
|
||||
return CustomWorldPublicationStatus;
|
||||
@@ -232,7 +284,7 @@ export const CustomWorldProfile = __t.object("CustomWorldProfile", {
|
||||
export type CustomWorldProfile = __Infer<typeof CustomWorldProfile>;
|
||||
|
||||
export const CustomWorldProfileView = __t.object("CustomWorldProfileView", {
|
||||
ownerUserId: __t.string(),
|
||||
ownerAccountId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
payloadJson: __t.string(),
|
||||
get visibility() {
|
||||
@@ -262,7 +314,7 @@ export type CustomWorldPublicationStatus = __Infer<typeof CustomWorldPublication
|
||||
|
||||
export const CustomWorldSession = __t.object("CustomWorldSession", {
|
||||
id: __t.string(),
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
sessionId: __t.string(),
|
||||
payloadJson: __t.string(),
|
||||
createdAtMs: __t.u64(),
|
||||
@@ -315,7 +367,7 @@ export const MutationResult = __t.object("MutationResult", {
|
||||
export type MutationResult = __Infer<typeof MutationResult>;
|
||||
|
||||
export const PlatformBrowseHistoryView = __t.object("PlatformBrowseHistoryView", {
|
||||
ownerUserId: __t.string(),
|
||||
ownerAccountId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
worldName: __t.string(),
|
||||
subtitle: __t.string(),
|
||||
@@ -330,7 +382,7 @@ export const PlatformBrowseHistoryView = __t.object("PlatformBrowseHistoryView",
|
||||
export type PlatformBrowseHistoryView = __Infer<typeof PlatformBrowseHistoryView>;
|
||||
|
||||
export const PlatformBrowseHistoryWriteInput = __t.object("PlatformBrowseHistoryWriteInput", {
|
||||
ownerUserId: __t.string(),
|
||||
ownerAccountId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
worldName: __t.string(),
|
||||
subtitle: __t.string(),
|
||||
@@ -345,7 +397,7 @@ export const PlatformBrowseHistoryWriteInput = __t.object("PlatformBrowseHistory
|
||||
export type PlatformBrowseHistoryWriteInput = __Infer<typeof PlatformBrowseHistoryWriteInput>;
|
||||
|
||||
export const ProfileDashboardState = __t.object("ProfileDashboardState", {
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
walletBalance: __t.i64(),
|
||||
totalPlayTimeMs: __t.u64(),
|
||||
updatedAtMs: __t.u64(),
|
||||
@@ -362,9 +414,9 @@ export type ProfileDashboardView = __Infer<typeof ProfileDashboardView>;
|
||||
|
||||
export const ProfilePlayedWorld = __t.object("ProfilePlayedWorld", {
|
||||
id: __t.string(),
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
worldKey: __t.string(),
|
||||
ownerUserId: __t.option(__t.string()),
|
||||
ownerAccountId: __t.option(__t.string()),
|
||||
profileId: __t.option(__t.string()),
|
||||
worldType: __t.option(__t.string()),
|
||||
worldTitle: __t.string(),
|
||||
@@ -377,7 +429,7 @@ export type ProfilePlayedWorld = __Infer<typeof ProfilePlayedWorld>;
|
||||
|
||||
export const ProfilePlayedWorldView = __t.object("ProfilePlayedWorldView", {
|
||||
worldKey: __t.string(),
|
||||
ownerUserId: __t.option(__t.string()),
|
||||
ownerAccountId: __t.option(__t.string()),
|
||||
profileId: __t.option(__t.string()),
|
||||
worldType: __t.option(__t.string()),
|
||||
worldTitle: __t.string(),
|
||||
@@ -390,7 +442,7 @@ export type ProfilePlayedWorldView = __Infer<typeof ProfilePlayedWorldView>;
|
||||
|
||||
export const ProfileWalletLedger = __t.object("ProfileWalletLedger", {
|
||||
id: __t.string(),
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
amountDelta: __t.i64(),
|
||||
balanceAfter: __t.i64(),
|
||||
sourceType: __t.string(),
|
||||
@@ -409,7 +461,7 @@ export const ProfileWalletLedgerView = __t.object("ProfileWalletLedgerView", {
|
||||
export type ProfileWalletLedgerView = __Infer<typeof ProfileWalletLedgerView>;
|
||||
|
||||
export const PublishedCustomWorldProfileView = __t.object("PublishedCustomWorldProfileView", {
|
||||
ownerUserId: __t.string(),
|
||||
ownerAccountId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
payloadJson: __t.string(),
|
||||
get visibility() {
|
||||
@@ -445,7 +497,7 @@ export const RiskBlockScopeType = __t.enum("RiskBlockScopeType", {
|
||||
export type RiskBlockScopeType = __Infer<typeof RiskBlockScopeType>;
|
||||
|
||||
export const RuntimeSetting = __t.object("RuntimeSetting", {
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
musicVolume: __t.f32(),
|
||||
updatedAtMs: __t.u64(),
|
||||
});
|
||||
@@ -457,7 +509,7 @@ export const RuntimeSettingsView = __t.object("RuntimeSettingsView", {
|
||||
export type RuntimeSettingsView = __Infer<typeof RuntimeSettingsView>;
|
||||
|
||||
export const SaveSnapshot = __t.object("SaveSnapshot", {
|
||||
userId: __t.string(),
|
||||
accountId: __t.string(),
|
||||
version: __t.u32(),
|
||||
savedAtMs: __t.u64(),
|
||||
bottomTab: __t.string(),
|
||||
@@ -537,58 +589,6 @@ export const SnapshotView = __t.object("SnapshotView", {
|
||||
});
|
||||
export type SnapshotView = __Infer<typeof SnapshotView>;
|
||||
|
||||
export const User = __t.object("User", {
|
||||
id: __t.string(),
|
||||
identity: __t.identity(),
|
||||
username: __t.option(__t.string()),
|
||||
passwordHash: __t.option(__t.string()),
|
||||
tokenVersion: __t.u32(),
|
||||
displayName: __t.string(),
|
||||
get loginProvider() {
|
||||
return LoginProvider;
|
||||
},
|
||||
get accountStatus() {
|
||||
return AccountStatus;
|
||||
},
|
||||
phoneNumber: __t.option(__t.string()),
|
||||
phoneVerifiedAtMs: __t.option(__t.u64()),
|
||||
createdAtMs: __t.u64(),
|
||||
updatedAtMs: __t.u64(),
|
||||
});
|
||||
export type User = __Infer<typeof User>;
|
||||
|
||||
export const UserBrowseHistory = __t.object("UserBrowseHistory", {
|
||||
id: __t.string(),
|
||||
userId: __t.string(),
|
||||
ownerUserId: __t.string(),
|
||||
profileId: __t.string(),
|
||||
worldName: __t.string(),
|
||||
subtitle: __t.string(),
|
||||
summaryText: __t.string(),
|
||||
coverImageSrc: __t.option(__t.string()),
|
||||
get themeMode() {
|
||||
return CustomWorldThemeMode;
|
||||
},
|
||||
authorDisplayName: __t.string(),
|
||||
visitedAtMs: __t.u64(),
|
||||
});
|
||||
export type UserBrowseHistory = __Infer<typeof UserBrowseHistory>;
|
||||
|
||||
export const UserSession = __t.object("UserSession", {
|
||||
id: __t.string(),
|
||||
userId: __t.string(),
|
||||
refreshTokenHash: __t.string(),
|
||||
clientType: __t.string(),
|
||||
userAgent: __t.option(__t.string()),
|
||||
ip: __t.option(__t.string()),
|
||||
expiresAtMs: __t.option(__t.u64()),
|
||||
revokedAtMs: __t.option(__t.u64()),
|
||||
createdAtMs: __t.u64(),
|
||||
updatedAtMs: __t.u64(),
|
||||
lastSeenAtMs: __t.u64(),
|
||||
});
|
||||
export type UserSession = __Infer<typeof UserSession>;
|
||||
|
||||
export const VerificationPromptEvent = __t.object("VerificationPromptEvent", {
|
||||
targetIdentity: __t.identity(),
|
||||
phoneNumberMasked: __t.option(__t.string()),
|
||||
|
||||
@@ -77,7 +77,7 @@ function mapBindingStatus(row: AuthStateView): AuthBindingStatus {
|
||||
|
||||
export function mapAuthUser(row: AuthStateView): AuthUser {
|
||||
return {
|
||||
id: row.userId,
|
||||
id: row.accountId,
|
||||
username: row.displayName,
|
||||
displayName: row.displayName,
|
||||
phoneNumberMasked: row.phoneNumberMasked ?? null,
|
||||
@@ -171,7 +171,7 @@ export function mapPlayedWorldEntry(
|
||||
): ProfilePlayedWorkSummary {
|
||||
return {
|
||||
worldKey: row.worldKey,
|
||||
ownerUserId: row.ownerUserId ?? null,
|
||||
ownerAccountId: row.ownerAccountId ?? null,
|
||||
profileId: row.profileId ?? null,
|
||||
worldType: row.worldType ?? null,
|
||||
worldTitle: row.worldTitle,
|
||||
@@ -186,7 +186,7 @@ export function mapBrowseHistoryEntry(
|
||||
row: PlatformBrowseHistoryView,
|
||||
): PlatformBrowseHistoryEntry {
|
||||
return {
|
||||
ownerUserId: row.ownerUserId,
|
||||
ownerAccountId: row.ownerAccountId,
|
||||
profileId: row.profileId,
|
||||
worldName: row.worldName,
|
||||
subtitle: row.subtitle,
|
||||
@@ -202,7 +202,7 @@ export function mapCustomWorldLibraryEntry(
|
||||
row: CustomWorldProfileView,
|
||||
): CustomWorldLibraryEntry<CustomWorldProfile> {
|
||||
return {
|
||||
ownerUserId: row.ownerUserId,
|
||||
ownerAccountId: row.ownerAccountId,
|
||||
profileId: row.profileId,
|
||||
profile: parseJson<CustomWorldProfile>(row.payloadJson, {
|
||||
id: row.profileId,
|
||||
@@ -243,7 +243,7 @@ export function mapGalleryCard(
|
||||
row: CustomWorldGalleryCardView,
|
||||
): CustomWorldGalleryCard {
|
||||
return {
|
||||
ownerUserId: row.ownerUserId,
|
||||
ownerAccountId: row.ownerAccountId,
|
||||
profileId: row.profileId,
|
||||
visibility: enumTag(row.visibility) === 'Published' ? 'published' : 'draft',
|
||||
publishedAt: bigintToIso(row.publishedAtMs),
|
||||
@@ -263,7 +263,7 @@ export function mapPublishedProfile(
|
||||
row: PublishedCustomWorldProfileView,
|
||||
): CustomWorldLibraryEntry<CustomWorldProfile> {
|
||||
return mapCustomWorldLibraryEntry({
|
||||
ownerUserId: row.ownerUserId,
|
||||
ownerAccountId: row.ownerAccountId,
|
||||
profileId: row.profileId,
|
||||
payloadJson: row.payloadJson,
|
||||
visibility: row.visibility,
|
||||
|
||||
Reference in New Issue
Block a user