feat: WC2026 赛事 seed、生产上线初始化脚本与目录归档
重构 seed 为 WC2026 72 场小组赛与 48 强优胜盘;新增 production 模式仅保留 admin 与赛事示例;提供 prod-init-db 全量重置脚本;管理端 i18n 分包与赛事归档能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import { UsersService } from '../../domains/identity/users.service';
|
||||
import { AgentsService } from '../../domains/agent/agents.service';
|
||||
import { WalletService } from '../../domains/ledger/wallet.service';
|
||||
import { MatchesService } from '../../domains/catalog/matches.service';
|
||||
import { CatalogArchiveService } from '../../domains/catalog/catalog-archive.service';
|
||||
import { OutrightService } from '../../domains/catalog/outright.service';
|
||||
import { MarketsService } from '../../domains/odds/markets.service';
|
||||
import { SettlementService } from '../../domains/settlement/settlement.service';
|
||||
@@ -501,6 +502,10 @@ class CreatePlatformMatchDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
awayTeamLogoUrl?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
correctScoreEnabled?: boolean;
|
||||
}
|
||||
|
||||
class UpdatePlatformMatchDto {
|
||||
@@ -554,6 +559,10 @@ class UpdatePlatformMatchDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
awayTeamLogoUrl?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
correctScoreEnabled?: boolean;
|
||||
}
|
||||
|
||||
class ReopenMatchDto {
|
||||
@@ -562,6 +571,16 @@ class ReopenMatchDto {
|
||||
startTime?: string;
|
||||
}
|
||||
|
||||
class ArchiveMatchDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
force?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
refundPendingBets?: boolean;
|
||||
}
|
||||
|
||||
class BatchMatchOddsDto {
|
||||
@IsArray()
|
||||
updates!: OutrightOddsUpdateItemDto[];
|
||||
@@ -924,6 +943,7 @@ export class AdminController {
|
||||
private agents: AgentsService,
|
||||
private wallet: WalletService,
|
||||
private matches: MatchesService,
|
||||
private catalogArchive: CatalogArchiveService,
|
||||
private outright: OutrightService,
|
||||
private markets: MarketsService,
|
||||
private settlement: SettlementService,
|
||||
@@ -948,6 +968,31 @@ export class AdminController {
|
||||
return jsonResponse(overview);
|
||||
}
|
||||
|
||||
@Get('users/page-init')
|
||||
@RequirePermissions(P.agentsView)
|
||||
async getUsersPageInit() {
|
||||
const [
|
||||
playerSettings,
|
||||
bettingLimits,
|
||||
hierarchySettings,
|
||||
platformDirect,
|
||||
agentLevelCounts,
|
||||
] = await Promise.all([
|
||||
this.systemConfig.getPlayerAccountSettings(),
|
||||
this.bettingLimits.getLimits(),
|
||||
this.systemConfig.getAgentHierarchySettings(),
|
||||
this.systemConfig.getPlatformDirectCashbackSettings(),
|
||||
this.agents.countAgentsByLevel(),
|
||||
]);
|
||||
return jsonResponse({
|
||||
playerSettings,
|
||||
bettingLimits,
|
||||
hierarchySettings,
|
||||
platformDirect,
|
||||
agentLevelCounts,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('users/settings/account')
|
||||
@RequirePermissions(P.settings)
|
||||
async getPlayerAccountSettings() {
|
||||
@@ -1126,6 +1171,23 @@ export class AdminController {
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
|
||||
@Delete('users/:id')
|
||||
@RequirePermissions(P.usersCreate)
|
||||
async deletePlayer(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
await this.users.softDeletePlayer(BigInt(id));
|
||||
await this.audit.log({
|
||||
operatorId,
|
||||
operatorType: 'ADMIN',
|
||||
action: 'DELETE_PLAYER',
|
||||
module: 'USERS',
|
||||
targetId: id,
|
||||
});
|
||||
return jsonResponse({ deleted: true });
|
||||
}
|
||||
|
||||
@Post('users')
|
||||
@RequirePermissions(P.usersCreate)
|
||||
async createPlayer(
|
||||
@@ -1184,9 +1246,20 @@ export class AdminController {
|
||||
|
||||
@Get('agents/options')
|
||||
@RequirePermissions(P.agentsView)
|
||||
async listAgentOptions() {
|
||||
async listAgentOptions(
|
||||
@Query('keyword') keyword?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
const take = Math.min(100, Math.max(1, parseInt(limit ?? '50', 10) || 50));
|
||||
const kw = keyword?.trim();
|
||||
const agents = await this.prisma.user.findMany({
|
||||
where: { userType: 'AGENT', deletedAt: null },
|
||||
where: {
|
||||
userType: 'AGENT',
|
||||
deletedAt: null,
|
||||
...(kw
|
||||
? { username: { contains: kw, mode: 'insensitive' as const } }
|
||||
: {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
@@ -1194,6 +1267,7 @@ export class AdminController {
|
||||
parent: { select: { username: true } },
|
||||
},
|
||||
orderBy: [{ agentLevel: 'asc' }, { username: 'asc' }],
|
||||
take,
|
||||
});
|
||||
return jsonResponse(
|
||||
agents.map((a) => ({
|
||||
@@ -1464,6 +1538,20 @@ export class AdminController {
|
||||
return jsonResponse(league);
|
||||
}
|
||||
|
||||
@Get('leagues/:leagueId/archive-preview')
|
||||
@RequirePermissions(P.matches)
|
||||
async getLeagueArchivePreview(@Param('leagueId') leagueId: string) {
|
||||
const preview = await this.catalogArchive.getLeagueArchivePreview(BigInt(leagueId));
|
||||
return jsonResponse(preview);
|
||||
}
|
||||
|
||||
@Post('leagues/:leagueId/archive')
|
||||
@RequirePermissions(P.matches)
|
||||
async archiveLeague(@Param('leagueId') leagueId: string) {
|
||||
const result = await this.catalogArchive.archiveLeague(BigInt(leagueId));
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Get('leagues')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async listLeagues(
|
||||
@@ -1499,13 +1587,17 @@ export class AdminController {
|
||||
@Query('status') status?: string,
|
||||
@Query('keyword') keyword?: string,
|
||||
@Query('locale') locale?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
const items = await this.matches.listAdminLeagueMatches(BigInt(leagueId), {
|
||||
const result = await this.matches.listAdminLeagueMatches(BigInt(leagueId), {
|
||||
status: status || undefined,
|
||||
keyword: keyword || undefined,
|
||||
locale: locale || undefined,
|
||||
page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
|
||||
pageSize: pageSize ? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 20)) : 20,
|
||||
});
|
||||
return jsonResponse({ items });
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('teams')
|
||||
@@ -1581,6 +1673,7 @@ export class AdminController {
|
||||
groupName: dto.groupName,
|
||||
homeTeamLogoUrl: dto.homeTeamLogoUrl,
|
||||
awayTeamLogoUrl: dto.awayTeamLogoUrl,
|
||||
correctScoreEnabled: dto.correctScoreEnabled,
|
||||
updatedBy: operatorId,
|
||||
});
|
||||
await this.outright.syncOutrightTeamsForLeagueIfExists(match.leagueId);
|
||||
@@ -1594,6 +1687,28 @@ export class AdminController {
|
||||
return jsonResponse({ deleted: true });
|
||||
}
|
||||
|
||||
@Get('matches/:id/archive-preview')
|
||||
@RequirePermissions(P.matches)
|
||||
async getMatchArchivePreview(@Param('id') id: string) {
|
||||
const preview = await this.catalogArchive.getMatchArchivePreview(BigInt(id));
|
||||
return jsonResponse(preview);
|
||||
}
|
||||
|
||||
@Post('matches/:id/archive')
|
||||
@RequirePermissions(P.matches)
|
||||
async archiveMatch(@Param('id') id: string, @Body() dto: ArchiveMatchDto) {
|
||||
const matchId = BigInt(id);
|
||||
const result = await this.catalogArchive.archiveMatch(matchId, {
|
||||
force: dto.force === true,
|
||||
});
|
||||
let voidedCount = 0;
|
||||
if (dto.refundPendingBets) {
|
||||
const voided = await this.settlement.voidMatchBets(matchId);
|
||||
voidedCount = voided.voidedCount;
|
||||
}
|
||||
return jsonResponse({ ...result, voidedCount });
|
||||
}
|
||||
|
||||
@Post('matches')
|
||||
@RequirePermissions(P.matches)
|
||||
async createMatch(@CurrentUser('id') operatorId: bigint, @Body() dto: CreatePlatformMatchDto) {
|
||||
@@ -1612,6 +1727,7 @@ export class AdminController {
|
||||
awayTeamMs: dto.awayTeamMs,
|
||||
startTime: new Date(dto.startTime),
|
||||
isHot: dto.isHot,
|
||||
correctScoreEnabled: dto.correctScoreEnabled,
|
||||
displayOrder: dto.displayOrder,
|
||||
matchName: dto.matchName,
|
||||
stage: dto.stage,
|
||||
@@ -1642,6 +1758,13 @@ export class AdminController {
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/unpublish')
|
||||
@RequirePermissions(P.matches)
|
||||
async unpublishMatch(@Param('id') id: string) {
|
||||
const match = await this.matches.unpublishMatch(BigInt(id));
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/close')
|
||||
@RequirePermissions(P.matches)
|
||||
async closeMatch(@Param('id') id: string) {
|
||||
@@ -1880,6 +2003,27 @@ export class AdminController {
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Get('matches/:id/settlement/summary')
|
||||
@RequirePermissions(P.settlement, P.reports)
|
||||
async getMatchSettlementSummary(@Param('id') id: string) {
|
||||
const data = await this.settlement.getMatchBetStatsSummary(BigInt(id));
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Get('matches/:id/settlement/bets')
|
||||
@RequirePermissions(P.settlement, P.reports)
|
||||
async getMatchSettlementBets(
|
||||
@Param('id') id: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
const data = await this.settlement.getMatchBetStatsBets(BigInt(id), {
|
||||
page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
|
||||
pageSize: pageSize ? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 10)) : 10,
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Get('matches/:id/settlement/stats')
|
||||
@RequirePermissions(P.settlement, P.reports)
|
||||
async getMatchSettlementStats(
|
||||
@@ -2495,4 +2639,30 @@ export class AdminController {
|
||||
);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('deposit-orders/:id/reopen')
|
||||
@RequirePermissions(P.depositReview)
|
||||
async reopenDepositOrder(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
const result = await this.depositService.reopenDepositOrderForReview(
|
||||
BigInt(id),
|
||||
operatorId,
|
||||
);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Delete('deposit-orders/:id')
|
||||
@RequirePermissions(P.depositReview)
|
||||
async deleteDepositOrder(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
const result = await this.depositService.deleteDepositOrder(
|
||||
BigInt(id),
|
||||
operatorId,
|
||||
);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
@@ -176,9 +177,18 @@ export class AgentPortalController {
|
||||
}
|
||||
|
||||
@Get('players')
|
||||
async listPlayers(@CurrentUser('id') agentId: bigint) {
|
||||
const players = await this.agents.getDirectPlayers(agentId);
|
||||
return jsonResponse(players);
|
||||
async listPlayers(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
const result = await this.agents.getDirectPlayers(agentId, {
|
||||
page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
|
||||
pageSize: pageSize
|
||||
? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 20))
|
||||
: 100,
|
||||
});
|
||||
return jsonResponse(result.items);
|
||||
}
|
||||
|
||||
@Get('players/scoped')
|
||||
@@ -261,6 +271,15 @@ export class AgentPortalController {
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
|
||||
@Delete('players/:id')
|
||||
async deletePlayer(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@Param('id') playerId: string,
|
||||
) {
|
||||
await this.agents.deleteDirectPlayer(agentId, BigInt(playerId));
|
||||
return jsonResponse({ deleted: true });
|
||||
}
|
||||
|
||||
@Get('agents')
|
||||
async listSubAgents(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number) {
|
||||
const maxLevel = await this.agents.getMaxAgentLevel();
|
||||
@@ -401,13 +420,23 @@ export class AgentPortalController {
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@CurrentUser('agentLevel') level: number,
|
||||
@Param('id') subAgentId: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
if (!(await this.canManageSubAgents(level))) {
|
||||
return jsonResponse([]);
|
||||
return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
|
||||
}
|
||||
await this.agents.assertDescendantAgent(agentId, BigInt(subAgentId));
|
||||
const players = await this.agents.getPortalAgentDirectPlayers(agentId, BigInt(subAgentId));
|
||||
return jsonResponse(players);
|
||||
const result = await this.agents.getPortalAgentDirectPlayers(
|
||||
agentId,
|
||||
BigInt(subAgentId),
|
||||
{
|
||||
page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
|
||||
pageSize: pageSize
|
||||
? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 20))
|
||||
: 20,
|
||||
},
|
||||
);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('agents/:id/credit')
|
||||
|
||||
Reference in New Issue
Block a user