2026-06-11 01:01:53 +08:00

388 lines
14 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;
}
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 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 type DiceConfigPayload = Omit<DiceConfigDto, "appCode" | "createdAtMs" | "updatedAtMs" | "poolBalanceCoin">;
export interface DicePoolAdjustPayload {
amountCoin: number;
direction: "in" | "out";
reason: string;
}
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">;
export interface GameCatalogQuery {
platformCode?: string;
status?: string;
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 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 adjustDicePool(
gameId: string,
payload: DicePoolAdjustPayload,
): Promise<{ config: DiceConfigDto; adjustment: unknown }> {
const endpoint = API_ENDPOINTS.adjustDicePool;
return apiRequest<{ config: DiceConfigDto; adjustment: unknown }, DicePoolAdjustPayload>(
apiEndpointPath(API_OPERATIONS.adjustDicePool, { game_id: gameId }),
{ body: payload, method: endpoint.method },
).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 = "dice") {
const endpoint = API_ENDPOINTS.setDiceRobotStatus;
return apiRequest<DiceRobotDto, { status: string }>(
apiEndpointPath(API_OPERATIONS.setDiceRobotStatus, { user_id: userId }),
{ body: { status }, method: endpoint.method, query: { gameId } },
).then(normalizeDiceRobot);
}
export function deleteDiceRobot(userId: string, gameId = "dice"): Promise<{ deleted: boolean }> {
const endpoint = API_ENDPOINTS.deleteDiceRobot;
return apiRequest<{ deleted: boolean }>(apiEndpointPath(API_OPERATIONS.deleteDiceRobot, { user_id: userId }), {
method: endpoint.method,
query: { gameId },
});
}
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),
};
}
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 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 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";
}