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; export type SelfGameNewUserPolicyPayload = Omit; 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; 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 { const endpoint = API_ENDPOINTS.listPlatforms; return apiRequest(apiEndpointPath(API_OPERATIONS.listPlatforms), { method: endpoint.method, query: { status }, }).then((page) => ({ ...page, items: (page.items || []).map(normalizePlatform), })); } export function upsertGamePlatform(payload: GamePlatformPayload, platformCode = ""): Promise { const operation = platformCode ? API_OPERATIONS.updatePlatform : API_OPERATIONS.createPlatform; const endpoint = API_ENDPOINTS[operation]; return apiRequest( apiEndpointPath(operation, platformCode ? { platform_code: platformCode } : {}), { body: payload, method: endpoint.method, }, ).then(normalizePlatform); } export function listGameCatalog(query: GameCatalogQuery = {}): Promise { const endpoint = API_ENDPOINTS.listCatalog; return apiRequest(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 { const operation = gameId ? API_OPERATIONS.updateCatalog : API_OPERATIONS.createCatalog; const endpoint = API_ENDPOINTS[operation]; return apiRequest( apiEndpointPath(operation, gameId ? { game_id: gameId } : {}), { body: payload, method: endpoint.method, }, ).then(normalizeCatalog); } export function setGameStatus(gameId: string, status: string): Promise { const endpoint = API_ENDPOINTS.setGameStatus; return apiRequest( apiEndpointPath(API_OPERATIONS.setGameStatus, { game_id: gameId }), { body: { status }, method: endpoint.method, }, ).then(normalizeCatalog); } export function deleteGameCatalog(gameId: string): Promise { const endpoint = API_ENDPOINTS.deleteCatalog; return apiRequest(apiEndpointPath(API_OPERATIONS.deleteCatalog, { game_id: gameId }), { method: endpoint.method, }); } export function setGameWhitelistEnabled(gameId: string, enabled: boolean): Promise { const endpoint = API_ENDPOINTS.setGameWhitelistEnabled; return apiRequest( 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 { const endpoint = API_ENDPOINTS.addGameWhitelistUser; return apiRequest( apiEndpointPath(API_OPERATIONS.addGameWhitelistUser, { game_id: gameId }), { body: { userId }, method: endpoint.method }, ).then(normalizeGameWhitelistUser); } export function deleteGameWhitelistUser(gameId: string, userId: string): Promise { const endpoint = API_ENDPOINTS.deleteGameWhitelistUser; return apiRequest( apiEndpointPath(API_OPERATIONS.deleteGameWhitelistUser, { game_id: gameId, user_id: userId }), { method: endpoint.method }, ); } export function syncGamePlatformCatalog(platformCode: string, payload: GameSyncPayload = {}): Promise { return apiRequest( `/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 { const endpoint = API_ENDPOINTS.updateDiceConfig; return apiRequest( apiEndpointPath(API_OPERATIONS.updateDiceConfig, { game_id: gameId }), { body: payload, method: endpoint.method }, ).then(normalizeDiceConfig); } export function getSelfGameNewUserPolicy(gameId: string): Promise { return apiRequest( `/v1/admin/game/self-games/${encodeURIComponent(gameId)}/new-user-policy`, { method: "GET" }, ).then(normalizeSelfGameNewUserPolicy); } export function updateSelfGameNewUserPolicy( gameId: string, payload: SelfGameNewUserPolicyPayload, ): Promise { return apiRequest( `/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 { return apiRequest( `/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(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( 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 { const endpoint = API_ENDPOINTS.listRoomRpsChallenges; return apiRequest(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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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"; }