rwadurian/backend/services/trading-service/src/api/controllers/admin.controller.ts

286 lines
9.3 KiB
TypeScript

import { Controller, Get, Post, Body, Query, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
import { TradingConfigRepository } from '../../infrastructure/persistence/repositories/trading-config.repository';
import { OrderRepository } from '../../infrastructure/persistence/repositories/order.repository';
import { OrderType, OrderStatus, OrderSource } from '../../domain/aggregates/order.aggregate';
import { Public } from '../../shared/guards/jwt-auth.guard';
class SetBuyEnabledDto {
@IsBoolean()
enabled: boolean;
}
class SetDepthEnabledDto {
@IsBoolean()
enabled: boolean;
}
@ApiTags('Admin')
@Controller('admin')
export class AdminController {
constructor(
private readonly prisma: PrismaService,
private readonly tradingConfigRepository: TradingConfigRepository,
private readonly orderRepository: OrderRepository,
) {}
@Get('accounts/sync')
@Public()
@ApiOperation({ summary: '获取所有交易账户用于同步' })
async getAllAccountsForSync() {
const accounts = await this.prisma.tradingAccount.findMany({
select: {
accountSequence: true,
shareBalance: true,
cashBalance: true,
frozenShares: true,
frozenCash: true,
totalBought: true,
totalSold: true,
createdAt: true,
updatedAt: true,
},
});
return {
accounts: accounts.map((acc) => ({
accountSequence: acc.accountSequence,
shareBalance: acc.shareBalance.toString(),
cashBalance: acc.cashBalance.toString(),
frozenShares: acc.frozenShares.toString(),
frozenCash: acc.frozenCash.toString(),
totalBought: acc.totalBought.toString(),
totalSold: acc.totalSold.toString(),
createdAt: acc.createdAt,
updatedAt: acc.updatedAt,
})),
total: accounts.length,
};
}
@Get('trading/status')
@Public()
@ApiOperation({ summary: '获取交易系统状态' })
@ApiResponse({ status: 200, description: '返回交易系统配置状态' })
async getTradingStatus() {
const config = await this.tradingConfigRepository.getConfig();
if (!config) {
return {
initialized: false,
isActive: false,
buyEnabled: false,
activatedAt: null,
message: '交易系统未初始化',
};
}
return {
initialized: true,
isActive: config.isActive,
buyEnabled: config.buyEnabled,
activatedAt: config.activatedAt,
totalShares: config.totalShares.toFixed(8),
burnTarget: config.burnTarget.toFixed(8),
burnPeriodMinutes: config.burnPeriodMinutes,
minuteBurnRate: config.minuteBurnRate.toFixed(18),
message: config.isActive ? '交易系统已激活' : '交易系统未激活',
};
}
@Get('trading/buy-enabled')
@Public()
@ApiOperation({ summary: '获取买入功能开关状态' })
@ApiResponse({ status: 200, description: '返回买入功能是否启用' })
async getBuyEnabled() {
const config = await this.tradingConfigRepository.getConfig();
return {
enabled: config?.buyEnabled ?? false,
};
}
@Post('trading/buy-enabled')
@Public() // TODO: 生产环境应添加管理员权限验证
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '设置买入功能开关' })
@ApiResponse({ status: 200, description: '买入功能开关设置成功' })
async setBuyEnabled(@Body() dto: SetBuyEnabledDto) {
await this.tradingConfigRepository.setBuyEnabled(dto.enabled);
return {
success: true,
enabled: dto.enabled,
message: dto.enabled ? '买入功能已开启' : '买入功能已关闭',
};
}
@Post('trading/activate')
@Public() // TODO: 生产环境应添加管理员权限验证
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '激活交易/销毁系统' })
@ApiResponse({ status: 200, description: '交易系统已激活' })
@ApiResponse({ status: 400, description: '交易系统未初始化' })
async activateTrading() {
const config = await this.tradingConfigRepository.getConfig();
if (!config) {
return {
success: false,
message: '交易系统未初始化,请先启动服务',
};
}
if (config.isActive) {
return {
success: true,
message: '交易系统已处于激活状态',
activatedAt: config.activatedAt,
};
}
await this.tradingConfigRepository.activate();
const updatedConfig = await this.tradingConfigRepository.getConfig();
return {
success: true,
message: '交易系统已激活,每分钟销毁已开始运行',
activatedAt: updatedConfig?.activatedAt,
};
}
@Post('trading/deactivate')
@Public() // TODO: 生产环境应添加管理员权限验证
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '关闭交易/销毁系统' })
@ApiResponse({ status: 200, description: '交易系统已关闭' })
async deactivateTrading() {
const config = await this.tradingConfigRepository.getConfig();
if (!config) {
return {
success: false,
message: '交易系统未初始化',
};
}
if (!config.isActive) {
return {
success: true,
message: '交易系统已处于关闭状态',
};
}
await this.tradingConfigRepository.deactivate();
return {
success: true,
message: '交易系统已关闭,每分钟销毁已暂停',
};
}
@Get('trading/depth-enabled')
@Public()
@ApiOperation({ summary: '获取深度显示开关状态' })
@ApiResponse({ status: 200, description: '返回深度显示是否启用' })
async getDepthEnabled() {
const config = await this.tradingConfigRepository.getConfig();
return {
enabled: config?.depthEnabled ?? false,
};
}
@Get('orders')
@Public()
@ApiOperation({ summary: '获取全局订单列表' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'pageSize', required: false, type: Number })
@ApiQuery({ name: 'type', required: false, enum: ['BUY', 'SELL'] })
@ApiQuery({ name: 'status', required: false, enum: ['PENDING', 'PARTIAL', 'FILLED', 'CANCELLED'] })
@ApiQuery({ name: 'source', required: false, enum: ['USER', 'MARKET_MAKER', 'DEX_BOT', 'SYSTEM'] })
@ApiQuery({ name: 'search', required: false, type: String })
@ApiQuery({ name: 'startDate', required: false, type: String })
@ApiQuery({ name: 'endDate', required: false, type: String })
async getOrders(
@Query('page') page?: number,
@Query('pageSize') pageSize?: number,
@Query('type') type?: string,
@Query('status') status?: string,
@Query('source') source?: string,
@Query('search') search?: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
) {
const p = page ? Number(page) : 1;
const ps = pageSize ? Number(pageSize) : 20;
const result = await this.orderRepository.findAllOrders({
type: type as OrderType | undefined,
status: status as OrderStatus | undefined,
source: source as OrderSource | undefined,
search: search || undefined,
startDate: startDate ? new Date(startDate) : undefined,
endDate: endDate ? new Date(endDate) : undefined,
page: p,
pageSize: ps,
});
return {
data: result.data,
total: result.total,
page: p,
pageSize: ps,
totalPages: Math.ceil(result.total / ps),
};
}
@Get('trades')
@Public()
@ApiOperation({ summary: '获取全局成交记录' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'pageSize', required: false, type: Number })
@ApiQuery({ name: 'source', required: false, enum: ['USER', 'MARKET_MAKER', 'DEX_BOT', 'SYSTEM'] })
@ApiQuery({ name: 'search', required: false, type: String })
@ApiQuery({ name: 'startDate', required: false, type: String })
@ApiQuery({ name: 'endDate', required: false, type: String })
async getTrades(
@Query('page') page?: number,
@Query('pageSize') pageSize?: number,
@Query('source') source?: string,
@Query('search') search?: string,
@Query('startDate') startDate?: string,
@Query('endDate') endDate?: string,
) {
const p = page ? Number(page) : 1;
const ps = pageSize ? Number(pageSize) : 20;
const result = await this.orderRepository.findAllTrades({
source: source || undefined,
search: search || undefined,
startDate: startDate ? new Date(startDate) : undefined,
endDate: endDate ? new Date(endDate) : undefined,
page: p,
pageSize: ps,
});
return {
data: result.data,
total: result.total,
page: p,
pageSize: ps,
totalPages: Math.ceil(result.total / ps),
};
}
@Post('trading/depth-enabled')
@Public() // TODO: 生产环境应添加管理员权限验证
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '设置深度显示开关' })
@ApiResponse({ status: 200, description: '深度显示开关设置成功' })
async setDepthEnabled(@Body() dto: SetDepthEnabledDto) {
await this.tradingConfigRepository.setDepthEnabled(dto.enabled);
return {
success: true,
enabled: dto.enabled,
message: dto.enabled ? '深度显示已开启' : '深度显示已关闭',
};
}
}