388 lines
14 KiB
TypeScript
388 lines
14 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;
|
||
}
|
||
|
||
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";
|
||
}
|