2026-07-14 15:09:35 +08:00

781 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { apiRequest } from "@/shared/api/request";
import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints";
import type { QueryParams } from "@/shared/api/request";
export interface GamePlatformDto {
appCode?: string;
platformCode: string;
platformName: string;
status: string;
apiBaseUrl: string;
// 服务端按 adapterType 选择真实厂商协议yomi_v4/leadercc_v1/zeeone_v1/baishun_v1/vivagames_v1/reyou_v1。
adapterType: string;
// callbackSecret 由后台列表返回,用于配置页直接核对和轮换厂商 key。
callbackSecret?: string;
callbackSecretSet: boolean;
callbackIpWhitelist: string[];
// 厂商扩展配置保留 JSON 字符串,后台前端不绑定具体字段结构。
adapterConfigJson: string;
sortOrder: number;
createdAtMs?: number;
updatedAtMs?: number;
}
export interface GameCatalogDto {
appCode?: string;
gameId: string;
platformCode: string;
providerGameId: string;
gameName: string;
category: string;
iconUrl: string;
coverUrl: string;
launchUrl?: string;
launchMode: string;
orientation: string;
safeHeight: number;
minCoin: number;
status: string;
sortOrder: number;
tags: string[];
createdAtMs?: number;
updatedAtMs?: number;
whitelistEnabled: boolean;
}
export interface GameWhitelistUserDto {
userId: string;
displayUserId?: string;
username?: string;
avatar?: string;
status?: string;
createdByAdminId?: number;
createdAtMs?: number;
}
export interface GameCatalogPage {
items: GameCatalogDto[];
nextCursor?: string;
pageSize?: number;
serverTimeMs?: number;
total?: number;
totalPages?: number;
}
export interface GamePlatformPage {
items: GamePlatformDto[];
serverTimeMs?: number;
}
export interface GameSyncPayload {
dryRun?: boolean;
status?: string;
category?: string;
launchMode?: string;
minCoin?: number;
tags?: string[];
gameListType?: number;
providerGameIds?: string[];
}
export interface GameSyncResult {
platformCode: string;
synced: number;
dryRun: boolean;
items: GameCatalogDto[];
}
export interface DiceStakeOptionDto {
stakeCoin: number;
enabled: boolean;
sortOrder: number;
}
export interface DiceConfigDto {
appCode?: string;
gameId: string;
status: string;
stakeOptions: DiceStakeOptionDto[];
feeBps: number;
poolBps: number;
minPlayers: number;
maxPlayers: number;
robotEnabled: boolean;
robotMatchWaitMs: number;
poolBalanceCoin: number;
createdAtMs?: number;
updatedAtMs?: number;
}
export interface SelfGameNewUserPolicyDto {
appCode?: string;
gameId: string;
enabled: boolean;
protectionRounds: number;
protectionHours: number;
maxStakeCoin: number;
lifetimeSubsidyQuotaCoin: number;
day1SubsidyQuotaCoin: number;
singleRoundSubsidyCapCoin: number;
strategyLevel: string;
loseStreakProtectionEnabled: boolean;
loseStreakTrigger: number;
normalPhaseTargetWinRatePercent: number;
blackPoolRobotForceWinEnabled: boolean;
createdAtMs?: number;
updatedAtMs?: number;
}
export interface SelfGameStakePoolDto {
appCode?: string;
gameId: string;
stakeCoin: number;
balanceCoin: number;
reservedCoin: number;
availableCoin: number;
targetBalanceCoin: number;
safeBalanceCoin: number;
warningBalanceCoin: number;
dangerBalanceCoin: number;
status: string;
poolLevel: string;
requiredPoolCoin: number;
humanWinCapacity: number;
botMatchEnabled: boolean;
blackReason?: string;
createdAtMs?: number;
updatedAtMs?: number;
}
export interface DiceRobotDto {
appCode?: string;
gameId: string;
userId: string;
userIdNumber?: number;
shortId?: string;
nickname?: string;
avatar?: string;
status: string;
createdByAdminId?: number;
createdAtMs?: number;
updatedAtMs?: number;
}
export interface DiceRobotPage {
items: DiceRobotDto[];
nextCursor?: string;
pageSize?: number;
serverTimeMs?: number;
}
export interface RoomRpsStakeGiftDto {
giftId: string;
giftIdNumber?: number;
giftName?: string;
giftIconUrl?: string;
giftPriceCoin?: number;
enabled: boolean;
sortOrder: number;
}
export interface RoomRpsConfigDto {
appCode?: string;
gameId: string;
status: string;
challengeTimeoutMs: number;
revealCountdownMs: number;
stakeGifts: RoomRpsStakeGiftDto[];
createdAtMs?: number;
updatedAtMs?: number;
}
export interface RoomRpsPlayerDto {
userId: string;
userIdNumber?: number;
gesture: string;
result: string;
balanceAfter?: number;
joinedAtMs?: number;
}
export interface RoomRpsChallengeDto {
appCode?: string;
challengeId: string;
roomId: string;
regionId?: number;
status: string;
stakeGiftId: string;
stakeGiftIdNumber?: number;
stakeCoin: number;
initiator: RoomRpsPlayerDto;
challenger: RoomRpsPlayerDto;
winnerUserId: string;
winnerUserIdNumber?: number;
settlementStatus: string;
failureReason?: string;
timeoutAtMs?: number;
revealAtMs?: number;
createdAtMs?: number;
matchedAtMs?: number;
settledAtMs?: number;
updatedAtMs?: number;
}
export interface RoomRpsChallengePage {
items: RoomRpsChallengeDto[];
nextCursor?: string;
pageSize?: number;
serverTimeMs?: number;
}
export type DiceConfigPayload = Omit<DiceConfigDto, "appCode" | "createdAtMs" | "updatedAtMs" | "poolBalanceCoin">;
export type SelfGameNewUserPolicyPayload = Omit<SelfGameNewUserPolicyDto, "appCode" | "createdAtMs" | "updatedAtMs">;
export interface RoomRpsConfigPayload {
status: string;
challengeTimeoutMs: number;
revealCountdownMs: number;
stakeGifts: Array<{
giftId: string;
enabled: boolean;
sortOrder: number;
}>;
}
export interface DicePoolAdjustPayload {
amountCoin: number;
direction: "in" | "out";
reason: string;
}
export type SelfGameStakePoolPayload = Pick<
SelfGameStakePoolDto,
"targetBalanceCoin" | "safeBalanceCoin" | "warningBalanceCoin" | "dangerBalanceCoin" | "status"
>;
export interface DiceGenerateRobotsPayload {
count: number;
nicknameLanguage?: "english" | "arabic";
avatarUrls?: string[];
country?: string;
gender?: string;
}
export type GamePlatformPayload = Omit<
GamePlatformDto,
"appCode" | "createdAtMs" | "updatedAtMs" | "callbackSecretSet"
>;
export type GameCatalogPayload = Omit<GameCatalogDto, "appCode" | "createdAtMs" | "updatedAtMs" | "whitelistEnabled">;
export interface GameCatalogQuery {
platformCode?: string;
status?: string;
pageSize?: number;
cursor?: string;
}
export interface RoomRpsChallengeQuery {
roomId?: string;
status?: string;
initiatorUserId?: string;
challengerUserId?: string;
startTimeMs?: number;
endTimeMs?: number;
pageSize?: number;
cursor?: string;
}
export function listGamePlatforms(status = ""): Promise<GamePlatformPage> {
const endpoint = API_ENDPOINTS.listPlatforms;
return apiRequest<GamePlatformPage>(apiEndpointPath(API_OPERATIONS.listPlatforms), {
method: endpoint.method,
query: { status },
}).then((page) => ({
...page,
items: (page.items || []).map(normalizePlatform),
}));
}
export function upsertGamePlatform(payload: GamePlatformPayload, platformCode = ""): Promise<GamePlatformDto> {
const operation = platformCode ? API_OPERATIONS.updatePlatform : API_OPERATIONS.createPlatform;
const endpoint = API_ENDPOINTS[operation];
return apiRequest<GamePlatformDto, GamePlatformPayload>(
apiEndpointPath(operation, platformCode ? { platform_code: platformCode } : {}),
{
body: payload,
method: endpoint.method,
},
).then(normalizePlatform);
}
export function listGameCatalog(query: GameCatalogQuery = {}): Promise<GameCatalogPage> {
const endpoint = API_ENDPOINTS.listCatalog;
return apiRequest<GameCatalogPage>(apiEndpointPath(API_OPERATIONS.listCatalog), {
method: endpoint.method,
query: query as QueryParams,
}).then((page) => ({
...page,
items: (page.items || []).map(normalizeCatalog),
}));
}
export function upsertGameCatalog(payload: GameCatalogPayload, gameId = ""): Promise<GameCatalogDto> {
const operation = gameId ? API_OPERATIONS.updateCatalog : API_OPERATIONS.createCatalog;
const endpoint = API_ENDPOINTS[operation];
return apiRequest<GameCatalogDto, GameCatalogPayload>(
apiEndpointPath(operation, gameId ? { game_id: gameId } : {}),
{
body: payload,
method: endpoint.method,
},
).then(normalizeCatalog);
}
export function setGameStatus(gameId: string, status: string): Promise<GameCatalogDto> {
const endpoint = API_ENDPOINTS.setGameStatus;
return apiRequest<GameCatalogDto, { status: string }>(
apiEndpointPath(API_OPERATIONS.setGameStatus, { game_id: gameId }),
{
body: { status },
method: endpoint.method,
},
).then(normalizeCatalog);
}
export function deleteGameCatalog(gameId: string): Promise<void> {
const endpoint = API_ENDPOINTS.deleteCatalog;
return apiRequest<void>(apiEndpointPath(API_OPERATIONS.deleteCatalog, { game_id: gameId }), {
method: endpoint.method,
});
}
export function setGameWhitelistEnabled(gameId: string, enabled: boolean): Promise<GameCatalogDto> {
const endpoint = API_ENDPOINTS.setGameWhitelistEnabled;
return apiRequest<GameCatalogDto, { enabled: boolean }>(
apiEndpointPath(API_OPERATIONS.setGameWhitelistEnabled, { game_id: gameId }),
{ body: { enabled }, method: endpoint.method },
).then(normalizeCatalog);
}
export function listGameWhitelistUsers(
gameId: string,
): Promise<{ items: GameWhitelistUserDto[]; serverTimeMs?: number }> {
const endpoint = API_ENDPOINTS.listGameWhitelistUsers;
return apiRequest<{ items: GameWhitelistUserDto[]; serverTimeMs?: number }>(
apiEndpointPath(API_OPERATIONS.listGameWhitelistUsers, { game_id: gameId }),
{ method: endpoint.method, query: { pageSize: 200 } },
).then((result) => ({ ...result, items: (result.items || []).map(normalizeGameWhitelistUser) }));
}
export function addGameWhitelistUser(gameId: string, userId: string): Promise<GameWhitelistUserDto> {
const endpoint = API_ENDPOINTS.addGameWhitelistUser;
return apiRequest<GameWhitelistUserDto, { userId: string }>(
apiEndpointPath(API_OPERATIONS.addGameWhitelistUser, { game_id: gameId }),
{ body: { userId }, method: endpoint.method },
).then(normalizeGameWhitelistUser);
}
export function deleteGameWhitelistUser(gameId: string, userId: string): Promise<void> {
const endpoint = API_ENDPOINTS.deleteGameWhitelistUser;
return apiRequest<void>(
apiEndpointPath(API_OPERATIONS.deleteGameWhitelistUser, { game_id: gameId, user_id: userId }),
{ method: endpoint.method },
);
}
export function syncGamePlatformCatalog(platformCode: string, payload: GameSyncPayload = {}): Promise<GameSyncResult> {
return apiRequest<GameSyncResult, GameSyncPayload>(
`/v1/admin/game/platforms/${encodeURIComponent(platformCode)}/sync-games`,
{
body: payload,
method: "POST",
},
).then((result) => ({
...result,
items: (result.items || []).map(normalizeCatalog),
synced: numberValue(result.synced),
dryRun: Boolean(result.dryRun),
}));
}
export function listSelfGames(): Promise<{ items: DiceConfigDto[]; serverTimeMs?: number }> {
const endpoint = API_ENDPOINTS.listSelfGames;
return apiRequest<{ items: DiceConfigDto[]; serverTimeMs?: number }>(
apiEndpointPath(API_OPERATIONS.listSelfGames),
{
method: endpoint.method,
},
).then((page) => ({
...page,
items: (page.items || []).map(normalizeDiceConfig),
}));
}
export function updateDiceConfig(gameId: string, payload: DiceConfigPayload): Promise<DiceConfigDto> {
const endpoint = API_ENDPOINTS.updateDiceConfig;
return apiRequest<DiceConfigDto, DiceConfigPayload>(
apiEndpointPath(API_OPERATIONS.updateDiceConfig, { game_id: gameId }),
{ body: payload, method: endpoint.method },
).then(normalizeDiceConfig);
}
export function getSelfGameNewUserPolicy(gameId: string): Promise<SelfGameNewUserPolicyDto> {
return apiRequest<SelfGameNewUserPolicyDto>(
`/v1/admin/game/self-games/${encodeURIComponent(gameId)}/new-user-policy`,
{ method: "GET" },
).then(normalizeSelfGameNewUserPolicy);
}
export function updateSelfGameNewUserPolicy(
gameId: string,
payload: SelfGameNewUserPolicyPayload,
): Promise<SelfGameNewUserPolicyDto> {
return apiRequest<SelfGameNewUserPolicyDto, SelfGameNewUserPolicyPayload>(
`/v1/admin/game/self-games/${encodeURIComponent(gameId)}/new-user-policy`,
{ body: payload, method: "PATCH" },
).then(normalizeSelfGameNewUserPolicy);
}
export function listSelfGameStakePools(
gameId: string,
): Promise<{ items: SelfGameStakePoolDto[]; serverTimeMs?: number }> {
return apiRequest<{ items: SelfGameStakePoolDto[]; serverTimeMs?: number }>(
`/v1/admin/game/self-games/${encodeURIComponent(gameId)}/stake-pools`,
{ method: "GET" },
).then((page) => ({
...page,
items: (page.items || []).map(normalizeSelfGameStakePool),
}));
}
export function updateSelfGameStakePool(
gameId: string,
stakeCoin: number,
payload: SelfGameStakePoolPayload,
): Promise<SelfGameStakePoolDto> {
return apiRequest<SelfGameStakePoolDto, SelfGameStakePoolPayload>(
`/v1/admin/game/self-games/${encodeURIComponent(gameId)}/stake-pools/${encodeURIComponent(String(stakeCoin))}`,
{ body: payload, method: "PATCH" },
).then(normalizeSelfGameStakePool);
}
export function adjustSelfGameStakePool(
gameId: string,
stakeCoin: number,
payload: DicePoolAdjustPayload,
): Promise<{ config: DiceConfigDto; adjustment: unknown }> {
return apiRequest<{ config: DiceConfigDto; adjustment: unknown }, DicePoolAdjustPayload>(
`/v1/admin/game/self-games/${encodeURIComponent(gameId)}/stake-pools/${encodeURIComponent(String(stakeCoin))}/adjust`,
{ body: payload, method: "POST" },
).then((result) => ({ ...result, config: normalizeDiceConfig(result.config || {}) }));
}
export function listDiceRobots(query: { gameId?: string; status?: string; pageSize?: number; cursor?: string } = {}) {
const endpoint = API_ENDPOINTS.listDiceRobots;
return apiRequest<DiceRobotPage>(apiEndpointPath(API_OPERATIONS.listDiceRobots), {
method: endpoint.method,
query: query as QueryParams,
}).then((page) => ({
...page,
items: (page.items || []).map(normalizeDiceRobot),
}));
}
export function generateDiceRobots(payload: DiceGenerateRobotsPayload) {
const endpoint = API_ENDPOINTS.generateDiceRobots;
return apiRequest<{ created: number; items: DiceRobotDto[] }, DiceGenerateRobotsPayload>(
apiEndpointPath(API_OPERATIONS.generateDiceRobots),
{ body: payload, method: endpoint.method },
).then((result) => ({ ...result, items: (result.items || []).map(normalizeDiceRobot) }));
}
export function setDiceRobotStatus(userId: string, status: string, gameId = "") {
const endpoint = API_ENDPOINTS.setDiceRobotStatus;
return apiRequest<DiceRobotDto, { status: string }>(
apiEndpointPath(API_OPERATIONS.setDiceRobotStatus, { user_id: userId }),
{ body: { status }, method: endpoint.method, query: gameId ? { gameId } : undefined },
).then(normalizeDiceRobot);
}
export function deleteDiceRobot(userId: string, gameId = ""): Promise<{ deleted: boolean }> {
const endpoint = API_ENDPOINTS.deleteDiceRobot;
return apiRequest<{ deleted: boolean }>(apiEndpointPath(API_OPERATIONS.deleteDiceRobot, { user_id: userId }), {
method: endpoint.method,
query: gameId ? { gameId } : undefined,
});
}
export function getRoomRpsConfig(): Promise<{ config: RoomRpsConfigDto; serverTimeMs?: number }> {
const endpoint = API_ENDPOINTS.getRoomRpsConfig;
return apiRequest<{ config: RoomRpsConfigDto; serverTimeMs?: number }>(
apiEndpointPath(API_OPERATIONS.getRoomRpsConfig),
{ method: endpoint.method },
).then((result) => ({ ...result, config: normalizeRoomRpsConfig(result.config || {}) }));
}
export function updateRoomRpsConfig(
payload: RoomRpsConfigPayload,
): Promise<{ config: RoomRpsConfigDto; serverTimeMs?: number }> {
const endpoint = API_ENDPOINTS.updateRoomRpsConfig;
return apiRequest<{ config: RoomRpsConfigDto; serverTimeMs?: number }, RoomRpsConfigPayload>(
apiEndpointPath(API_OPERATIONS.updateRoomRpsConfig),
{ body: payload, method: endpoint.method },
).then((result) => ({ ...result, config: normalizeRoomRpsConfig(result.config || {}) }));
}
export function listRoomRpsChallenges(query: RoomRpsChallengeQuery = {}): Promise<RoomRpsChallengePage> {
const endpoint = API_ENDPOINTS.listRoomRpsChallenges;
return apiRequest<RoomRpsChallengePage>(apiEndpointPath(API_OPERATIONS.listRoomRpsChallenges), {
method: endpoint.method,
query: query as QueryParams,
}).then((page) => ({
...page,
items: (page.items || []).map(normalizeRoomRpsChallenge),
}));
}
export function getRoomRpsChallenge(
challengeId: string,
): Promise<{ challenge: RoomRpsChallengeDto; serverTimeMs?: number }> {
const endpoint = API_ENDPOINTS.getRoomRpsChallenge;
return apiRequest<{ challenge: RoomRpsChallengeDto; serverTimeMs?: number }>(
apiEndpointPath(API_OPERATIONS.getRoomRpsChallenge, { challenge_id: challengeId }),
{ method: endpoint.method },
).then((result) => ({ ...result, challenge: normalizeRoomRpsChallenge(result.challenge || {}) }));
}
export function retryRoomRpsSettlement(
challengeId: string,
): Promise<{ challenge: RoomRpsChallengeDto; serverTimeMs?: number }> {
const endpoint = API_ENDPOINTS.retryRoomRpsSettlement;
return apiRequest<{ challenge: RoomRpsChallengeDto; serverTimeMs?: number }>(
apiEndpointPath(API_OPERATIONS.retryRoomRpsSettlement, { challenge_id: challengeId }),
{ method: endpoint.method },
).then((result) => ({ ...result, challenge: normalizeRoomRpsChallenge(result.challenge || {}) }));
}
export function expireRoomRpsChallenge(
challengeId: string,
): Promise<{ challenge: RoomRpsChallengeDto; serverTimeMs?: number }> {
const endpoint = API_ENDPOINTS.expireRoomRpsChallenge;
return apiRequest<{ challenge: RoomRpsChallengeDto; serverTimeMs?: number }>(
apiEndpointPath(API_OPERATIONS.expireRoomRpsChallenge, { challenge_id: challengeId }),
{ method: endpoint.method },
).then((result) => ({ ...result, challenge: normalizeRoomRpsChallenge(result.challenge || {}) }));
}
function normalizePlatform(platform: Partial<GamePlatformDto>): GamePlatformDto {
return {
appCode: stringValue(platform.appCode),
platformCode: stringValue(platform.platformCode),
platformName: stringValue(platform.platformName),
status: stringValue(platform.status || "active"),
apiBaseUrl: stringValue(platform.apiBaseUrl),
adapterType: stringValue(platform.adapterType || "yomi_v4"),
callbackSecret: stringValue(platform.callbackSecret),
callbackSecretSet: Boolean(platform.callbackSecretSet),
callbackIpWhitelist: Array.isArray(platform.callbackIpWhitelist)
? platform.callbackIpWhitelist.map(String).filter(Boolean)
: [],
adapterConfigJson: stringValue(platform.adapterConfigJson || "{}"),
sortOrder: numberValue(platform.sortOrder),
createdAtMs: numberValue(platform.createdAtMs),
updatedAtMs: numberValue(platform.updatedAtMs),
};
}
function normalizeCatalog(game: Partial<GameCatalogDto>): GameCatalogDto {
return {
appCode: stringValue(game.appCode),
gameId: stringValue(game.gameId),
platformCode: stringValue(game.platformCode),
providerGameId: stringValue(game.providerGameId),
gameName: stringValue(game.gameName),
category: stringValue(game.category),
iconUrl: stringValue(game.iconUrl),
coverUrl: stringValue(game.coverUrl),
launchUrl: stringValue(game.launchUrl),
launchMode: normalizeLaunchMode(game.launchMode),
orientation: stringValue(game.orientation || "portrait"),
safeHeight: numberValue(game.safeHeight),
minCoin: numberValue(game.minCoin),
status: stringValue(game.status || "active"),
sortOrder: numberValue(game.sortOrder),
tags: Array.isArray(game.tags) ? game.tags.map(String).filter(Boolean) : [],
createdAtMs: numberValue(game.createdAtMs),
updatedAtMs: numberValue(game.updatedAtMs),
whitelistEnabled: game.whitelistEnabled === true,
};
}
function normalizeGameWhitelistUser(user: Partial<GameWhitelistUserDto>): GameWhitelistUserDto {
return {
userId: stringValue(user.userId),
displayUserId: stringValue(user.displayUserId),
username: stringValue(user.username),
avatar: stringValue(user.avatar),
status: stringValue(user.status),
createdByAdminId: numberValue(user.createdByAdminId),
createdAtMs: numberValue(user.createdAtMs),
};
}
function normalizeDiceConfig(config: Partial<DiceConfigDto>): DiceConfigDto {
return {
appCode: stringValue(config.appCode),
gameId: stringValue(config.gameId || "dice"),
status: stringValue(config.status || "active"),
stakeOptions: Array.isArray(config.stakeOptions)
? config.stakeOptions.map((item) => ({
stakeCoin: numberValue(item.stakeCoin),
enabled: item.enabled !== false,
sortOrder: numberValue(item.sortOrder),
}))
: [],
feeBps: numberValue(config.feeBps),
poolBps: numberValue(config.poolBps),
minPlayers: numberValue(config.minPlayers || 2),
maxPlayers: numberValue(config.maxPlayers || 2),
robotEnabled: config.robotEnabled !== false,
robotMatchWaitMs: numberValue(config.robotMatchWaitMs || 3000),
poolBalanceCoin: numberValue(config.poolBalanceCoin),
createdAtMs: numberValue(config.createdAtMs),
updatedAtMs: numberValue(config.updatedAtMs),
};
}
function normalizeSelfGameNewUserPolicy(policy: Partial<SelfGameNewUserPolicyDto>): SelfGameNewUserPolicyDto {
return {
appCode: stringValue(policy.appCode),
gameId: stringValue(policy.gameId || "dice"),
enabled: policy.enabled !== false,
protectionRounds: numberValue(policy.protectionRounds || 30),
protectionHours: numberValue(policy.protectionHours || 168),
maxStakeCoin: numberValue(policy.maxStakeCoin || 5000),
lifetimeSubsidyQuotaCoin: numberValue(policy.lifetimeSubsidyQuotaCoin || 30000),
day1SubsidyQuotaCoin: numberValue(policy.day1SubsidyQuotaCoin || 15000),
singleRoundSubsidyCapCoin: numberValue(policy.singleRoundSubsidyCapCoin || 5000),
strategyLevel: stringValue(policy.strategyLevel || "soft_boost"),
loseStreakProtectionEnabled: policy.loseStreakProtectionEnabled !== false,
loseStreakTrigger: numberValue(policy.loseStreakTrigger || 2),
normalPhaseTargetWinRatePercent: numberValue(policy.normalPhaseTargetWinRatePercent || 53),
blackPoolRobotForceWinEnabled: policy.blackPoolRobotForceWinEnabled === true,
createdAtMs: numberValue(policy.createdAtMs),
updatedAtMs: numberValue(policy.updatedAtMs),
};
}
function normalizeSelfGameStakePool(pool: Partial<SelfGameStakePoolDto>): SelfGameStakePoolDto {
return {
appCode: stringValue(pool.appCode),
gameId: stringValue(pool.gameId || "dice"),
stakeCoin: numberValue(pool.stakeCoin),
balanceCoin: numberValue(pool.balanceCoin),
reservedCoin: numberValue(pool.reservedCoin),
availableCoin: numberValue(pool.availableCoin),
targetBalanceCoin: numberValue(pool.targetBalanceCoin),
safeBalanceCoin: numberValue(pool.safeBalanceCoin),
warningBalanceCoin: numberValue(pool.warningBalanceCoin),
dangerBalanceCoin: numberValue(pool.dangerBalanceCoin),
status: stringValue(pool.status || "active"),
poolLevel: stringValue(pool.poolLevel || "black"),
requiredPoolCoin: numberValue(pool.requiredPoolCoin),
humanWinCapacity: numberValue(pool.humanWinCapacity),
botMatchEnabled: pool.botMatchEnabled === true,
blackReason: stringValue(pool.blackReason),
createdAtMs: numberValue(pool.createdAtMs),
updatedAtMs: numberValue(pool.updatedAtMs),
};
}
function normalizeDiceRobot(robot: Partial<DiceRobotDto>): DiceRobotDto {
return {
appCode: stringValue(robot.appCode),
gameId: stringValue(robot.gameId || "dice"),
userId: stringValue(robot.userId),
userIdNumber: numberValue(robot.userIdNumber),
shortId: stringValue(robot.shortId),
nickname: stringValue(robot.nickname),
avatar: stringValue(robot.avatar),
status: stringValue(robot.status || "active"),
createdByAdminId: numberValue(robot.createdByAdminId),
createdAtMs: numberValue(robot.createdAtMs),
updatedAtMs: numberValue(robot.updatedAtMs),
};
}
function normalizeRoomRpsConfig(config: Partial<RoomRpsConfigDto>): RoomRpsConfigDto {
return {
appCode: stringValue(config.appCode),
gameId: stringValue(config.gameId || "room_rps"),
status: stringValue(config.status || "active"),
challengeTimeoutMs: numberValue(config.challengeTimeoutMs || 600000),
revealCountdownMs: numberValue(config.revealCountdownMs || 3000),
stakeGifts: Array.isArray(config.stakeGifts) ? config.stakeGifts.map(normalizeRoomRpsStakeGift) : [],
createdAtMs: numberValue(config.createdAtMs),
updatedAtMs: numberValue(config.updatedAtMs),
};
}
function normalizeRoomRpsStakeGift(gift: Partial<RoomRpsStakeGiftDto>): RoomRpsStakeGiftDto {
return {
giftId: stringValue(gift.giftId),
giftIdNumber: numberValue(gift.giftIdNumber),
giftName: stringValue(gift.giftName),
giftIconUrl: stringValue(gift.giftIconUrl),
giftPriceCoin: numberValue(gift.giftPriceCoin),
enabled: gift.enabled !== false,
sortOrder: numberValue(gift.sortOrder),
};
}
function normalizeRoomRpsChallenge(challenge: Partial<RoomRpsChallengeDto>): RoomRpsChallengeDto {
return {
appCode: stringValue(challenge.appCode),
challengeId: stringValue(challenge.challengeId),
roomId: stringValue(challenge.roomId),
regionId: numberValue(challenge.regionId),
status: stringValue(challenge.status || "pending"),
stakeGiftId: stringValue(challenge.stakeGiftId),
stakeGiftIdNumber: numberValue(challenge.stakeGiftIdNumber),
stakeCoin: numberValue(challenge.stakeCoin),
initiator: normalizeRoomRpsPlayer(challenge.initiator || {}),
challenger: normalizeRoomRpsPlayer(challenge.challenger || {}),
winnerUserId: stringValue(challenge.winnerUserId),
winnerUserIdNumber: numberValue(challenge.winnerUserIdNumber),
settlementStatus: stringValue(challenge.settlementStatus),
failureReason: stringValue(challenge.failureReason),
timeoutAtMs: numberValue(challenge.timeoutAtMs),
revealAtMs: numberValue(challenge.revealAtMs),
createdAtMs: numberValue(challenge.createdAtMs),
matchedAtMs: numberValue(challenge.matchedAtMs),
settledAtMs: numberValue(challenge.settledAtMs),
updatedAtMs: numberValue(challenge.updatedAtMs),
};
}
function normalizeRoomRpsPlayer(player: Partial<RoomRpsPlayerDto>): RoomRpsPlayerDto {
return {
userId: stringValue(player.userId),
userIdNumber: numberValue(player.userIdNumber),
gesture: stringValue(player.gesture),
result: stringValue(player.result),
balanceAfter: numberValue(player.balanceAfter),
joinedAtMs: numberValue(player.joinedAtMs),
};
}
function stringValue(value: unknown) {
return typeof value === "string" ? value : value === undefined || value === null ? "" : String(value);
}
function numberValue(value: unknown) {
const number = Number(value || 0);
return Number.isFinite(number) ? number : 0;
}
function normalizeLaunchMode(value: unknown) {
const mode = stringValue(value);
return mode === "half_screen" || mode === "three_quarter_screen" || mode === "full_screen" ? mode : "full_screen";
}