781 lines
28 KiB
TypeScript
781 lines
28 KiB
TypeScript
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";
|
||
}
|