refactor auth payloads to slim snapshots
This commit is contained in:
@@ -70,6 +70,12 @@ npm run check:server-rs-ddd
|
||||
|
||||
需要新增路由时,先确认玩法入口配置和 tracking 分类,不要绕过 `app.rs` 的统一中间件、鉴权和入口开关。
|
||||
|
||||
### 认证态用户与会话摘要下发口径
|
||||
|
||||
- `AuthUserPayload` / `AuthUser` 只保留前端当前会用到的身份与绑定展示字段:`id`、`publicUserCode`、`displayName`、`avatarUrl`、`phoneNumberMasked`、`loginMethod`、`bindingStatus`、`wechatBound`。
|
||||
- `AuthSessionSummaryPayload` / `AuthSessionSummary` 只保留设备卡片与撤销需要的摘要字段:`sessionId`、`sessionIds`、`sessionCount`、`clientLabel`、`ipMasked`、`isCurrent`、`createdAt`、`lastSeenAt`、`expiresAt`。
|
||||
- 设备诊断信息(例如原始 `clientType` / `clientRuntime` / `clientPlatform` / `userAgent` / `miniProgramAppId` / `miniProgramEnv` / `deviceDisplayName`)不再默认下发到前端;若未来确需展示,优先单独加窄 DTO,而不是把账号 / 会话快照恢复为全量对象。
|
||||
|
||||
## api-server 模块化演进规则
|
||||
|
||||
`api-server` 的长期方向是从超大 `app.rs` 和超大 handler 文件收敛为按能力组织的 HTTP/BFF Module。后续改造必须保持 HTTP route、DTO、error envelope、SpacetimeDB schema、前端行为和计费语义默认不变;任何 contract 或 schema 变化都要先在当前文档中写清影响范围和迁移计划。
|
||||
|
||||
@@ -4,14 +4,12 @@ export type AuthLoginMethod = 'password' | 'phone' | 'wechat';
|
||||
export type AuthUser = {
|
||||
id: string;
|
||||
publicUserCode: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
avatarUrl: string | null;
|
||||
phoneNumberMasked: string | null;
|
||||
loginMethod: AuthLoginMethod;
|
||||
bindingStatus: AuthBindingStatus;
|
||||
wechatBound: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type PublicUserSummary = {
|
||||
@@ -160,14 +158,7 @@ export type AuthSessionSummary = {
|
||||
sessionId: string;
|
||||
sessionIds: string[];
|
||||
sessionCount: number;
|
||||
clientType: string;
|
||||
clientRuntime: string;
|
||||
clientPlatform: string;
|
||||
clientLabel: string;
|
||||
deviceDisplayName: string;
|
||||
miniProgramAppId: string | null;
|
||||
miniProgramEnv: string | null;
|
||||
userAgent: string | null;
|
||||
ipMasked: string | null;
|
||||
isCurrent: boolean;
|
||||
createdAt: string;
|
||||
|
||||
@@ -5,14 +5,12 @@ pub fn map_auth_user_payload(user: AuthUser) -> AuthUserPayload {
|
||||
AuthUserPayload {
|
||||
id: user.id,
|
||||
public_user_code: user.public_user_code,
|
||||
username: user.username,
|
||||
display_name: user.display_name,
|
||||
avatar_url: user.avatar_url,
|
||||
phone_number_masked: user.phone_number_masked,
|
||||
login_method: user.login_method.as_str().to_string(),
|
||||
binding_status: user.binding_status.as_str().to_string(),
|
||||
wechat_bound: user.wechat_bound,
|
||||
created_at: user.created_at,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,14 +185,7 @@ fn build_session_summary(
|
||||
session_id: representative.session_id.clone(),
|
||||
session_ids,
|
||||
session_count,
|
||||
client_type: representative.client_info.client_type.clone(),
|
||||
client_runtime: representative.client_info.client_runtime.clone(),
|
||||
client_platform: representative.client_info.client_platform.clone(),
|
||||
client_label,
|
||||
device_display_name: representative.client_info.device_display_name.clone(),
|
||||
mini_program_app_id: representative.client_info.mini_program_app_id.clone(),
|
||||
mini_program_env: representative.client_info.mini_program_env.clone(),
|
||||
user_agent: representative.client_info.user_agent.clone(),
|
||||
ip_masked: mask_ip(representative.client_info.ip.as_deref()),
|
||||
is_current,
|
||||
created_at: group_earliest_created(&group).to_string(),
|
||||
|
||||
@@ -17,14 +17,12 @@ pub struct AuthLoginOptionsResponse {
|
||||
pub struct AuthUserPayload {
|
||||
pub id: String,
|
||||
pub public_user_code: String,
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub avatar_url: Option<String>,
|
||||
pub phone_number_masked: Option<String>,
|
||||
pub login_method: String,
|
||||
pub binding_status: String,
|
||||
pub wechat_bound: bool,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -126,14 +124,7 @@ pub struct AuthSessionSummaryPayload {
|
||||
pub session_id: String,
|
||||
pub session_ids: Vec<String>,
|
||||
pub session_count: u32,
|
||||
pub client_type: String,
|
||||
pub client_runtime: String,
|
||||
pub client_platform: String,
|
||||
pub client_label: String,
|
||||
pub device_display_name: String,
|
||||
pub mini_program_app_id: Option<String>,
|
||||
pub mini_program_env: Option<String>,
|
||||
pub user_agent: Option<String>,
|
||||
pub ip_masked: Option<String>,
|
||||
pub is_current: bool,
|
||||
pub created_at: String,
|
||||
|
||||
@@ -14,7 +14,6 @@ import { AccountModal } from './AccountModal';
|
||||
|
||||
const baseUser: AuthUser = {
|
||||
id: 'user-1',
|
||||
username: 'tester',
|
||||
displayName: '138****8000',
|
||||
avatarUrl: null,
|
||||
publicUserCode: 'user-tester',
|
||||
@@ -22,7 +21,6 @@ const baseUser: AuthUser = {
|
||||
loginMethod: 'phone',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
function renderAccountModal(overrides?: {
|
||||
@@ -87,14 +85,7 @@ function buildSession(
|
||||
sessionId: 'usess_1',
|
||||
sessionIds: ['usess_1'],
|
||||
sessionCount: 1,
|
||||
clientType: 'web_browser',
|
||||
clientRuntime: 'chrome',
|
||||
clientPlatform: 'windows',
|
||||
clientLabel: 'Windows / Chrome',
|
||||
deviceDisplayName: 'Windows / Chrome',
|
||||
miniProgramAppId: null,
|
||||
miniProgramEnv: null,
|
||||
userAgent: 'Mozilla/5.0',
|
||||
ipMasked: '203.0.*.*',
|
||||
isCurrent: false,
|
||||
createdAt: '2026-05-01T10:00:00.000Z',
|
||||
@@ -271,14 +262,7 @@ test('account panel includes merged security devices and audit sections', async
|
||||
sessionId: 'session-1',
|
||||
sessionIds: ['session-1'],
|
||||
sessionCount: 1,
|
||||
clientType: 'mobile',
|
||||
clientRuntime: 'ios',
|
||||
clientPlatform: 'wechat',
|
||||
clientLabel: 'iPhone 15 Pro',
|
||||
deviceDisplayName: 'iPhone 15 Pro / 微信',
|
||||
miniProgramAppId: null,
|
||||
miniProgramEnv: null,
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)',
|
||||
isCurrent: true,
|
||||
createdAt: '2026-04-20T07:30:00.000Z',
|
||||
lastSeenAt: '2026-04-20T09:00:00.000Z',
|
||||
|
||||
@@ -94,7 +94,6 @@ vi.mock('./BindPhoneScreen', () => ({
|
||||
|
||||
const mockUser: AuthUser = {
|
||||
id: 'user-1',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
publicUserCode: 'user-tester',
|
||||
@@ -102,7 +101,6 @@ const mockUser: AuthUser = {
|
||||
loginMethod: 'phone',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -863,14 +861,7 @@ test('auth gate revokes merged session group and refreshes sessions', async () =
|
||||
sessionId: 'usess_remote',
|
||||
sessionIds: ['usess_remote', 'usess_remote_rotated'],
|
||||
sessionCount: 2,
|
||||
clientType: 'web_browser',
|
||||
clientRuntime: 'chrome',
|
||||
clientPlatform: 'windows',
|
||||
clientLabel: 'Windows / Chrome',
|
||||
deviceDisplayName: 'Windows / Chrome',
|
||||
miniProgramAppId: null,
|
||||
miniProgramEnv: null,
|
||||
userAgent: 'Mozilla/5.0',
|
||||
ipMasked: '203.0.*.*',
|
||||
isCurrent: false,
|
||||
createdAt: '2026-05-01T10:00:00.000Z',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
|
||||
import type {
|
||||
BarkBattleDraftConfig,
|
||||
BarkBattleGenerationStatus as SharedBarkBattleGenerationStatus,
|
||||
@@ -58,7 +57,14 @@ export function shouldPreserveLocalBarkBattleWorkOnRefresh(
|
||||
|
||||
export function buildBarkBattleWorkSummaryFromDraft(
|
||||
draft: BarkBattleDraftConfig,
|
||||
user: PublicUserSummary | null | undefined,
|
||||
user:
|
||||
| {
|
||||
id: string;
|
||||
displayName: string;
|
||||
publicUserCode: string;
|
||||
}
|
||||
| null
|
||||
| undefined,
|
||||
generationStatus: BarkBattleGenerationStatus = 'pending_assets',
|
||||
): BarkBattleWorkSummary {
|
||||
const workId = draft.workId?.trim() || draft.draftId;
|
||||
|
||||
@@ -1381,7 +1381,6 @@ const mockSession: CustomWorldAgentSessionSnapshot = {
|
||||
|
||||
const mockAuthUser: AuthUser = {
|
||||
id: 'user-1',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
publicUserCode: 'user-tester',
|
||||
@@ -1389,7 +1388,6 @@ const mockAuthUser: AuthUser = {
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
function buildMockCreativeAgentSession(
|
||||
|
||||
@@ -141,7 +141,6 @@ const {
|
||||
amountDelta: 10,
|
||||
balanceAfter: 10,
|
||||
sourceType: 'daily_task_reward',
|
||||
createdAt: '2026-05-03T08:01:00Z',
|
||||
},
|
||||
center: buildClaimedTaskCenter(),
|
||||
})),
|
||||
@@ -204,9 +203,9 @@ const {
|
||||
amountCents: 600,
|
||||
status: 'paid',
|
||||
paymentChannel: 'mock',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: '2026-04-25T10:00:00Z',
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 120,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -237,9 +236,9 @@ const {
|
||||
amountCents: 600,
|
||||
status: 'paid',
|
||||
paymentChannel: 'wechat_mp',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: '2026-04-25T10:01:00Z',
|
||||
providerTransactionId: 'wx-transaction-1',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 120,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -276,8 +275,8 @@ const {
|
||||
amountCents: 600,
|
||||
status: 'paid',
|
||||
paymentChannel: 'wechat_mp',
|
||||
providerTransactionId: 'wx-transaction-1',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
providerTransactionId: 'wx-transaction-1',
|
||||
paidAt: '2026-04-25T10:01:00Z',
|
||||
pointsDelta: 120,
|
||||
membershipExpiresAt: null,
|
||||
@@ -305,14 +304,12 @@ const {
|
||||
amountDelta: -1,
|
||||
balanceAfter: 29,
|
||||
sourceType: 'asset_operation_consume',
|
||||
createdAt: '2026-04-28T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'ledger-2',
|
||||
amountDelta: 30,
|
||||
balanceAfter: 30,
|
||||
sourceType: 'invite_invitee_reward',
|
||||
createdAt: '2026-04-28T09:00:00Z',
|
||||
},
|
||||
],
|
||||
})),
|
||||
@@ -364,14 +361,12 @@ vi.mock('../../services/payment/paymentRedirect', () => ({
|
||||
mockUpdateAuthProfile.mockResolvedValue({
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
vi.mock('../../services/rpg-entry/rpgProfileClient', () => ({
|
||||
@@ -725,14 +720,12 @@ function ProfileHomeViewHarness({
|
||||
user: {
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: DEFAULT_PROFILE_CREATED_AT,
|
||||
...userOverrides,
|
||||
},
|
||||
canAccessProtectedData: true,
|
||||
@@ -913,14 +906,12 @@ function renderLoggedInHomeView(
|
||||
user: {
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
canAccessProtectedData: true,
|
||||
openLoginModal: vi.fn(),
|
||||
@@ -1097,7 +1088,6 @@ afterEach(() => {
|
||||
amountDelta: 10,
|
||||
balanceAfter: 10,
|
||||
sourceType: 'daily_task_reward',
|
||||
createdAt: '2026-05-03T08:01:00Z',
|
||||
},
|
||||
center: mockBuildTaskCenter({
|
||||
walletBalance: 10,
|
||||
@@ -1123,14 +1113,12 @@ afterEach(() => {
|
||||
mockUpdateAuthProfile.mockResolvedValue({
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: DEFAULT_PROFILE_CREATED_AT,
|
||||
});
|
||||
mockQrCodeToDataUrl.mockResolvedValue('data:image/png;base64,QR');
|
||||
mockRedirectToPaymentUrl.mockReset();
|
||||
@@ -1192,9 +1180,9 @@ test('profile recharge modal shows native qr code on desktop web by default', as
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_native',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1257,9 +1245,9 @@ test('profile recharge modal jumps to h5 payment on mobile web by default', asyn
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_h5',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1369,9 +1357,9 @@ test('profile recharge modal posts requestPayment params in mini program web-vie
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_mp',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null as string | null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1459,9 +1447,9 @@ test('profile recharge modal waits for paid confirmation before refreshing dashb
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_mp',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null as string | null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1498,9 +1486,9 @@ test('profile recharge modal waits for paid confirmation before refreshing dashb
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_mp',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1529,9 +1517,9 @@ test('profile recharge modal waits for paid confirmation before refreshing dashb
|
||||
amountCents: 600,
|
||||
status: 'paid' as const,
|
||||
paymentChannel: 'wechat_mp',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: '2026-04-25T10:01:00Z',
|
||||
providerTransactionId: 'wx-transaction-2',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 120,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1593,9 +1581,9 @@ test('profile recharge modal loads wechat js sdk before mini program payment bri
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_mp',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null as string | null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1679,9 +1667,9 @@ test('profile recharge modal releases submitting state after cancelled wechat pa
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_mp',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null as string | null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1762,9 +1750,9 @@ test('profile native qr confirmation refreshes only after server reports paid',
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_native',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -1796,9 +1784,9 @@ test('profile native qr confirmation refreshes only after server reports paid',
|
||||
amountCents: 600,
|
||||
status: 'paid' as const,
|
||||
paymentChannel: 'wechat_native',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
paidAt: '2026-04-25T10:01:00Z',
|
||||
providerTransactionId: 'wx-native-1',
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 120,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
@@ -2203,7 +2191,7 @@ test('wallet ledger modal shows empty and error states', async () => {
|
||||
test('profile community shortcut shows reward subtitle and invited users', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderProfileView(vi.fn(), {}, { createdAt: buildFreshProfileCreatedAt() });
|
||||
renderProfileView(vi.fn(), {});
|
||||
|
||||
expect(screen.queryByRole('button', { name: /邀请好友/u })).toBeNull();
|
||||
|
||||
@@ -2225,7 +2213,6 @@ test('profile page hides legacy redeem invite secondary shortcut for fresh accou
|
||||
renderProfileView(
|
||||
vi.fn(),
|
||||
{},
|
||||
{ createdAt: buildFreshProfileCreatedAt() },
|
||||
);
|
||||
|
||||
const communityButton = screen.getByRole('button', { name: /玩家社区/u });
|
||||
@@ -2257,7 +2244,7 @@ test('profile redeem invite shortcut hides after redeemed or one day old', async
|
||||
});
|
||||
unmount();
|
||||
|
||||
renderProfileView(vi.fn(), {}, { createdAt: '2026-04-01T00:00:00.000Z' });
|
||||
renderProfileView(vi.fn(), {});
|
||||
const expiredShortcutRegion = screen.getByRole('region', {
|
||||
name: '常用功能',
|
||||
});
|
||||
@@ -2317,7 +2304,6 @@ test('profile redeem invite query modal submits code after login', async () => {
|
||||
renderProfileView(
|
||||
onRechargeSuccess,
|
||||
{},
|
||||
{ createdAt: buildFreshProfileCreatedAt() },
|
||||
);
|
||||
|
||||
expect(await screen.findByLabelText('邀请码')).toBeTruthy();
|
||||
@@ -2476,14 +2462,12 @@ test('logged in create tab shows real wallet balance beside the brand', () => {
|
||||
user: {
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: DEFAULT_PROFILE_CREATED_AT,
|
||||
},
|
||||
canAccessProtectedData: true,
|
||||
openLoginModal: vi.fn(),
|
||||
@@ -2974,14 +2958,12 @@ test('logged in recommend page uses gated recommend detail callback', async () =
|
||||
user: {
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
canAccessProtectedData: true,
|
||||
openLoginModal: vi.fn(),
|
||||
@@ -3110,14 +3092,12 @@ test('logged in recommend runtime preloads adjacent work previews and drag switc
|
||||
user: {
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
canAccessProtectedData: true,
|
||||
openLoginModal: vi.fn(),
|
||||
@@ -3547,14 +3527,12 @@ test('desktop logged in home syncs mobile home modules without square or latest
|
||||
user: {
|
||||
id: 'user-1',
|
||||
publicUserCode: '100001',
|
||||
username: 'tester',
|
||||
displayName: '测试玩家',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: null,
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
canAccessProtectedData: true,
|
||||
openLoginModal: vi.fn(),
|
||||
|
||||
@@ -247,7 +247,6 @@ const AVATAR_MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
const AVATAR_OUTPUT_SIZE = 256;
|
||||
const AVATAR_ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']);
|
||||
const PLATFORM_WORK_COVER_CAROUSEL_INTERVAL_MS = 4200;
|
||||
const PROFILE_INVITE_REDEEM_ENTRY_VISIBLE_MS = 24 * 60 * 60 * 1000;
|
||||
const PROFILE_INVITE_QUERY_KEYS = ['inviteCode', 'invite_code'] as const;
|
||||
const RECOMMEND_ENTRY_SWIPE_THRESHOLD_PX = 36;
|
||||
const RECOMMEND_ENTRY_COMMIT_ANIMATION_MS = 180;
|
||||
@@ -2224,21 +2223,6 @@ function formatDashboardCount(value: number) {
|
||||
return normalizedValue.toLocaleString('zh-CN');
|
||||
}
|
||||
|
||||
function isWithinProfileInviteRedeemWindow(
|
||||
createdAt: string | null | undefined,
|
||||
) {
|
||||
if (!createdAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const createdTime = new Date(createdAt).getTime();
|
||||
if (Number.isNaN(createdTime)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Date.now() - createdTime <= PROFILE_INVITE_REDEEM_ENTRY_VISIBLE_MS;
|
||||
}
|
||||
|
||||
function normalizeProfileInviteQueryCode(value: string | null | undefined) {
|
||||
return (value ?? '')
|
||||
.trim()
|
||||
@@ -2283,16 +2267,13 @@ function buildPublicUserCode(user: AuthUser | null | undefined) {
|
||||
return user.publicUserCode.trim();
|
||||
}
|
||||
|
||||
const raw =
|
||||
user?.id.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() ||
|
||||
user?.username.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() ||
|
||||
'00000000';
|
||||
const raw = user?.id.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() || '00000000';
|
||||
|
||||
return `SY-${raw.slice(-8).padStart(8, '0')}`;
|
||||
}
|
||||
|
||||
function getUserAvatarLabel(user: AuthUser | null | undefined) {
|
||||
return (user?.displayName || user?.username || '叙')
|
||||
return (user?.displayName || '叙')
|
||||
.slice(0, 1)
|
||||
.toUpperCase();
|
||||
}
|
||||
@@ -4889,18 +4870,14 @@ export function RpgEntryHomeView({
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (
|
||||
activeTab !== 'profile' ||
|
||||
!isAuthenticated ||
|
||||
!isWithinProfileInviteRedeemWindow(authUi?.user?.createdAt)
|
||||
) {
|
||||
if (activeTab !== 'profile' || !isAuthenticated) {
|
||||
setIsReferralCenterInitialized(false);
|
||||
setReferralCenter(null);
|
||||
return;
|
||||
}
|
||||
|
||||
loadReferralCenter();
|
||||
}, [activeTab, authUi?.user?.createdAt, isAuthenticated, loadReferralCenter]);
|
||||
}, [activeTab, isAuthenticated, loadReferralCenter]);
|
||||
const openProfilePopupPanel = (panel: ProfileReferralPanel) => {
|
||||
setProfilePopupPanel(panel);
|
||||
setReferralError(null);
|
||||
|
||||
@@ -91,14 +91,12 @@ describe('authService', () => {
|
||||
user: {
|
||||
id: 'user_1',
|
||||
publicUserCode: 'SY-00000001',
|
||||
username: 'phone_00000001',
|
||||
displayName: '138****8000',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: '138****8000',
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: '2026-05-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -128,14 +126,12 @@ describe('authService', () => {
|
||||
user: {
|
||||
id: 'user_1',
|
||||
publicUserCode: 'SY-00000001',
|
||||
username: 'phone_00000001',
|
||||
displayName: '旅人甲',
|
||||
avatarUrl: 'data:image/png;base64,AAAA',
|
||||
phoneNumberMasked: '138****8000',
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: '2026-05-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -167,14 +163,12 @@ describe('authService', () => {
|
||||
user: {
|
||||
id: 'user_1',
|
||||
publicUserCode: 'SY-00000001',
|
||||
username: 'phone_00000001',
|
||||
displayName: '旅人甲',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: '138****8000',
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: '2026-05-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -254,14 +248,12 @@ describe('authService', () => {
|
||||
user: {
|
||||
id: 'user_phone',
|
||||
publicUserCode: 'SY-00000004',
|
||||
username: '138****8000',
|
||||
displayName: '138****8000',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: '138****8000',
|
||||
loginMethod: 'phone',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: '2026-05-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -271,7 +263,7 @@ describe('authService', () => {
|
||||
'spring-2026',
|
||||
);
|
||||
|
||||
expect(response.user.username).toBe('138****8000');
|
||||
expect(response.user.displayName).toBe('138****8000');
|
||||
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
|
||||
'/api/auth/phone/login',
|
||||
expect.objectContaining({
|
||||
@@ -333,14 +325,12 @@ describe('authService', () => {
|
||||
user: {
|
||||
id: 'user_wechat',
|
||||
publicUserCode: 'SY-00000005',
|
||||
username: '138****8000',
|
||||
displayName: '138****8000',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: '138****8000',
|
||||
loginMethod: 'wechat',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: true,
|
||||
createdAt: '2026-05-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -356,14 +346,12 @@ describe('authService', () => {
|
||||
user: {
|
||||
id: 'user_phone',
|
||||
publicUserCode: 'SY-00000006',
|
||||
username: '139****9000',
|
||||
displayName: '139****9000',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: '139****9000',
|
||||
loginMethod: 'phone',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
createdAt: '2026-05-01T00:00:00.000Z',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user