重构 API 为 8 领域 + 应用层架构
将后端模块拆分为 domains、applications、shared 三层,结算计算器移入 domain 纯函数目录,API 路径与测试保持不变。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
189
apps/api/src/applications/agent/agent-portal.controller.ts
Normal file
189
apps/api/src/applications/agent/agent-portal.controller.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard, AgentGuard } from '../../domains/identity/guards';
|
||||
import { CurrentUser } from '../../shared/common/decorators';
|
||||
import { jsonResponse } from '../../shared/common/filters';
|
||||
import { AgentsService } from '../../domains/agent/agents.service';
|
||||
import { WalletService } from '../../domains/ledger/wallet.service';
|
||||
import { BetsService } from '../../domains/betting/bets.service';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { IsString, IsNumber, MinLength, IsOptional } from 'class-validator';
|
||||
|
||||
class CreatePlayerDto {
|
||||
@IsString()
|
||||
username!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
password!: string;
|
||||
}
|
||||
|
||||
class CreateSubAgentDto extends CreatePlayerDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
creditLimit?: number;
|
||||
}
|
||||
|
||||
class TransferDto {
|
||||
@IsNumber()
|
||||
amount!: number;
|
||||
|
||||
@IsString()
|
||||
requestId!: string;
|
||||
}
|
||||
|
||||
class CreditDto extends TransferDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
@ApiTags('Agent Portal')
|
||||
@Controller('agent')
|
||||
@UseGuards(JwtAuthGuard, AgentGuard)
|
||||
@ApiBearerAuth()
|
||||
export class AgentPortalController {
|
||||
constructor(
|
||||
private agents: AgentsService,
|
||||
private wallet: WalletService,
|
||||
private bets: BetsService,
|
||||
private prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
@Get('profile')
|
||||
async profile(@CurrentUser('id') agentId: bigint) {
|
||||
const profile = await this.agents.getProfile(agentId);
|
||||
return jsonResponse(profile);
|
||||
}
|
||||
|
||||
@Get('players')
|
||||
async listPlayers(@CurrentUser('id') agentId: bigint) {
|
||||
const players = await this.agents.getDirectPlayers(agentId);
|
||||
return jsonResponse(players);
|
||||
}
|
||||
|
||||
@Post('players')
|
||||
async createPlayer(@CurrentUser('id') agentId: bigint, @Body() dto: CreatePlayerDto) {
|
||||
const user = await this.agents.createPlayer(agentId, {
|
||||
username: dto.username,
|
||||
password: dto.password,
|
||||
parentId: agentId,
|
||||
});
|
||||
return jsonResponse(user);
|
||||
}
|
||||
|
||||
@Get('agents')
|
||||
async listSubAgents(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number) {
|
||||
if (level !== 1) {
|
||||
return jsonResponse([]);
|
||||
}
|
||||
const agents = await this.agents.getChildAgents(agentId);
|
||||
return jsonResponse(agents);
|
||||
}
|
||||
|
||||
@Post('agents')
|
||||
async createSubAgent(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number, @Body() dto: CreateSubAgentDto) {
|
||||
if (level !== 1) {
|
||||
return jsonResponse(null, 'Only level 1 agents can create sub-agents');
|
||||
}
|
||||
const user = await this.agents.createAgent(agentId, {
|
||||
username: dto.username,
|
||||
password: dto.password,
|
||||
level: 2,
|
||||
parentAgentId: agentId,
|
||||
creditLimit: dto.creditLimit,
|
||||
});
|
||||
return jsonResponse(user);
|
||||
}
|
||||
|
||||
@Post('players/:id/deposit')
|
||||
async depositToPlayer(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@Param('id') playerId: string,
|
||||
@Body() dto: TransferDto,
|
||||
) {
|
||||
const result = await this.agents.depositToPlayer(agentId, BigInt(playerId), dto.amount, dto.requestId);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('players/:id/withdraw')
|
||||
async withdrawFromPlayer(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@Param('id') playerId: string,
|
||||
@Body() dto: TransferDto,
|
||||
) {
|
||||
const result = await this.agents.withdrawFromPlayer(agentId, BigInt(playerId), dto.amount, dto.requestId);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('agents/:id/credit')
|
||||
async allocateCredit(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@Param('id') subAgentId: string,
|
||||
@Body() dto: CreditDto,
|
||||
) {
|
||||
const subAgent = await this.prisma.agentProfile.findUnique({
|
||||
where: { userId: BigInt(subAgentId) },
|
||||
});
|
||||
if (!subAgent || subAgent.parentAgentId !== agentId) {
|
||||
return jsonResponse(null, 'Not your sub-agent');
|
||||
}
|
||||
const result = await this.agents.adjustCredit(
|
||||
BigInt(subAgentId),
|
||||
dto.amount,
|
||||
agentId,
|
||||
dto.requestId,
|
||||
dto.remark,
|
||||
);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Get('bets')
|
||||
async listBets(@CurrentUser('id') agentId: bigint, @Query('page') page?: string) {
|
||||
const skip = ((page ? parseInt(page) : 1) - 1) * 20;
|
||||
const descendants = await this.prisma.agentClosure.findMany({
|
||||
where: { ancestorId: agentId },
|
||||
});
|
||||
const agentIds = descendants.map((d) => d.descendantId);
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.bet.findMany({
|
||||
where: { agentId: { in: agentIds } },
|
||||
include: { selections: true, user: true },
|
||||
orderBy: { placedAt: 'desc' },
|
||||
skip,
|
||||
take: 20,
|
||||
}),
|
||||
this.prisma.bet.count({ where: { agentId: { in: agentIds } } }),
|
||||
]);
|
||||
return jsonResponse({ items, total });
|
||||
}
|
||||
|
||||
@Get('reports/summary')
|
||||
async reportSummary(@CurrentUser('id') agentId: bigint) {
|
||||
const summary = await this.agents.getReportSummary(agentId);
|
||||
return jsonResponse(summary);
|
||||
}
|
||||
|
||||
@Get('wallet-transactions')
|
||||
async walletTransactions(@CurrentUser('id') agentId: bigint, @Query('playerId') playerId?: string) {
|
||||
const players = playerId
|
||||
? [BigInt(playerId)]
|
||||
: (await this.agents.getDirectPlayers(agentId)).map((p) => p.id);
|
||||
|
||||
const transactions = await this.prisma.walletTransaction.findMany({
|
||||
where: { userId: { in: players } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 50,
|
||||
});
|
||||
return jsonResponse(transactions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user