f0cad9dddc
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m28s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m26s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m41s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m56s
Project CI / Backend tests (pull_request) Successful in 9m4s
Project CI / Repository checks (pull_request) Successful in 7m13s
Project CI / Native shell tests (pull_request) Successful in 10m16s
Project CI / Frontend tests (pull_request) Successful in 2m26s
- 同步 master 侧 248 个提交:会员与泥点计费(PR #588 / #603)、创作者主页与关注粉丝(PR #638)、运行页刷新真重载(PR #640)、AGC 模板 templateVersion 递增(PR #643)等;确认 master 未触碰发行网关与 `api-server/src/modules/game_distribution.rs`。 - 冲突 1(server-rs/crates/module-runtime/src/domain.rs:1157-1162)`RuntimeProfileWalletLedgerSourceType` 两侧各自在末尾追加变体:保留 master 的 `MembershipUpgradeGrant` 为索引 16(线上已落库口径),本分支 `GamePurchase` 顺延为索引 17,并保留其「每账号每游戏一次扣费」注释。 - 冲突 2(server-rs/crates/module-runtime/src/domain.rs:1184-1188)`as_str()` 同时保留 `membership_upgrade_grant` 与 `game_purchase` 两个分支。 - 冲突 3(server-rs/crates/spacetime-client/src/active/mapper/runtime_profile.rs:95-101)bindings→领域枚举映射同时保留 `MembershipUpgradeGrant` 与 `GamePurchase` 两支,按新索引顺序排列。 - 冲突 4(server-rs/crates/spacetime-client/src/module_bindings/runtime_profile_wallet_ledger_source_type_type.rs:43-47)生成枚举按模块源码顺序合并为 …`MembershipUpgradeGrant`、`GamePurchase`;随后 `npm run spacetime:generate` 复核该文件与生成结果逐字节一致。 - 冲突 5(server-rs/crates/api-server/src/admin.rs:4849-4853)sats 索引映射改为 `16 => membership_upgrade_grant` 与 `17 => game_purchase` 两条并存。 - 冲突 6(server-rs/crates/api-server/src/admin.rs:6368-6461)索引断言并入 master 的 `[16] => membership_upgrade_grant`,并把 master 的「17 尚未映射」占位断言替换为 `[17] => game_purchase`;本分支的后台消耗统计两个用例原样保留。 - 冲突 7(server-rs/crates/api-server/src/runtime_profile.rs:219-226)来源文案格式化同时保留 `MembershipUpgradeGrant` 与 `GamePurchase` 两支,注释随 `GamePurchase` 保留。 - 冲突 8(docs/project-memory/shared-memory/decision-log.md:3-24)两条决策记录并列保留:本分支 2026-10-05「播放会话前缀在三处入口清空 Cookie」在前,master 2026-10-03「每日免费发放额为 0 时前端隐藏该池」在后。 - 保住 master 侧:发行响应 ETag/304/gzip(`release_asset_response_with_cache` 五参形态与 `release_asset_etag` / `if_none_match_matches` / `accepts_gzip_encoding` / `gzip_release_asset` / `release_asset_not_modified_response`)、共享加载面 `PlatformGameLoadingSurface` 及其使用方、三池钱包与会员档位目录改造。 - 保住本分支侧:付费作品 404 守卫(`price_mud_points > 0` 且位于 `load_package` 之前)、购买与播放会话网关路由、nginx×3 / Pingora / Vite 的播放会话 Cookie 清空与 `nginx-route-parity.matrix.json` 登记、viewer 感知公开详情、后台审核价格、`PlatformGamePricingField` 共享定价组件与两端接入、AGC 项目版本只读 + 平台派发下一版本号、schema 与迁移白名单、钱包来源 `game_purchase`。 - 验证:`git merge-base --is-ancestor origin/master HEAD` 返回 0;`check:encoding`、`check:doc-index`、`check:spacetime-schema`、`check:server-rs-ddd`、`check:rustfmt`、`check:npm-workspaces`、`check:game-distribution-dto-parity`、`check:game-distribution-price-limit-parity`、`check:pingora-route-parity`、`check:nginx-spa-routes`、根/admin-web/AGC typecheck、`git diff --check` 全绿。 - 测试:`cargo test -p module-game-distribution` 30 passed,`cargo test -p api-server game_distribution` 61 passed,`cargo test -p spacetime-module game_distribution` 7 passed;定向 vitest 28 个文件 252 个用例全通过。 - `npm run spacetime:generate` 除 11 个 procedure 文件的纯空白格式差异外无任何语义改动(`git diff -w` 为空),已保留 master 的生成产物形态,提交内无 schema/绑定语义变化。
113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
import type {
|
|
ProfileWalletLedgerEntry,
|
|
ProfileWalletLedgerResponse,
|
|
} from '../../contracts/runtime';
|
|
|
|
const PROFILE_WALLET_LEDGER_SOURCE_LABELS = {
|
|
new_user_registration_reward: '注册赠送',
|
|
points_recharge: '泥点充值',
|
|
invite_inviter_reward: '邀请奖励',
|
|
invite_invitee_reward: '填写邀请码奖励',
|
|
snapshot_sync: '账户同步',
|
|
membership_period_grant: '会员周期发放',
|
|
membership_period_reset: '会员周期重置',
|
|
membership_upgrade_grant: '会员升级补点',
|
|
daily_free_grant: '每日免费发放',
|
|
daily_free_reset: '每日免费重置',
|
|
asset_operation_consume: '资产操作消耗',
|
|
llm_router_consume: 'LLM 调用消耗',
|
|
asset_operation_refund: '资产操作退回',
|
|
recharge_refund_recovery: '充值退款追回',
|
|
redeem_code_reward: '兑换码奖励',
|
|
puzzle_author_incentive_claim: '拼图作者奖励',
|
|
daily_task_reward: '每日任务奖励',
|
|
game_purchase: '购买游戏',
|
|
} satisfies Record<ProfileWalletLedgerEntry['sourceType'], string>;
|
|
|
|
export type ProfileWalletLedgerEntryPresentation = {
|
|
amountLabel: string;
|
|
balanceLabel: string;
|
|
createdAtLabel: string;
|
|
id: string;
|
|
isIncome: boolean;
|
|
sourceLabel: string;
|
|
};
|
|
|
|
export type ProfileWalletLedgerPresentation = {
|
|
balance: number;
|
|
balanceLabel: string;
|
|
entries: ProfileWalletLedgerEntryPresentation[];
|
|
};
|
|
|
|
export function getWalletLedgerSourceLabel(
|
|
sourceType: string | null | undefined,
|
|
) {
|
|
const normalizedSourceType = sourceType?.trim() ?? '';
|
|
if (!normalizedSourceType) {
|
|
return '未知来源';
|
|
}
|
|
|
|
return (
|
|
PROFILE_WALLET_LEDGER_SOURCE_LABELS[
|
|
normalizedSourceType as ProfileWalletLedgerEntry['sourceType']
|
|
] ?? normalizedSourceType
|
|
);
|
|
}
|
|
|
|
export function formatWalletLedgerAmount(amountDelta: number) {
|
|
return amountDelta > 0 ? `+${amountDelta}` : `${amountDelta}`;
|
|
}
|
|
|
|
export function formatWalletLedgerDate(value: string) {
|
|
const normalized = value.trim();
|
|
const numericTimestamp = normalized.match(/^(-?\d+(?:\.\d+)?)(?:Z)?$/u);
|
|
let date: Date;
|
|
|
|
if (numericTimestamp?.[1]) {
|
|
const rawTimestamp = Number(numericTimestamp[1]);
|
|
const absoluteTimestamp = Math.abs(rawTimestamp);
|
|
const timestampMs =
|
|
absoluteTimestamp >= 1_000_000_000_000_000
|
|
? rawTimestamp / 1000
|
|
: absoluteTimestamp >= 1_000_000_000_000
|
|
? rawTimestamp
|
|
: absoluteTimestamp >= 1_000_000_000
|
|
? rawTimestamp * 1000
|
|
: Number.NaN;
|
|
date = new Date(timestampMs);
|
|
} else {
|
|
date = new Date(normalized);
|
|
}
|
|
|
|
if (Number.isNaN(date.getTime())) {
|
|
return value;
|
|
}
|
|
|
|
const year = date.getUTCFullYear();
|
|
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
export function buildWalletLedgerPresentation(
|
|
ledger: ProfileWalletLedgerResponse | null,
|
|
fallbackBalance: number,
|
|
): ProfileWalletLedgerPresentation {
|
|
const entries = ledger?.entries ?? [];
|
|
const balance = entries[0]?.balanceAfter ?? fallbackBalance;
|
|
|
|
return {
|
|
balance,
|
|
balanceLabel: `${balance}泥点`,
|
|
entries: entries.map((entry) => ({
|
|
amountLabel: formatWalletLedgerAmount(entry.amountDelta),
|
|
balanceLabel: `余额 ${entry.balanceAfter}`,
|
|
createdAtLabel: formatWalletLedgerDate(entry.createdAt),
|
|
id: entry.id,
|
|
isIncome: entry.amountDelta > 0,
|
|
sourceLabel:
|
|
entry.reason?.trim() || getWalletLedgerSourceLabel(entry.sourceType),
|
|
})),
|
|
};
|
|
}
|