- Backend NestJS con módulos de autenticación, inventario, créditos - Frontend React con dashboard y componentes UI - Base de datos PostgreSQL con migraciones - Tests E2E configurados - Configuración de Docker y deployment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
249 lines
7.3 KiB
TypeScript
249 lines
7.3 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Patch,
|
|
Body,
|
|
Param,
|
|
Query,
|
|
UseGuards,
|
|
Req,
|
|
ParseUUIDPipe,
|
|
} from '@nestjs/common';
|
|
import { Request } from 'express';
|
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|
import { RolesGuard } from '../../common/guards/roles.guard';
|
|
import { Roles } from '../../common/decorators/roles.decorator';
|
|
import { UserRole } from '../users/entities/user.entity';
|
|
import { AdminDashboardService } from './services/admin-dashboard.service';
|
|
import { AdminProvidersService } from './services/admin-providers.service';
|
|
import { AdminPackagesService } from './services/admin-packages.service';
|
|
import { AdminPromotionsService } from './services/admin-promotions.service';
|
|
import { AdminModerationService } from './services/admin-moderation.service';
|
|
import { DashboardQueryDto, DashboardPeriod } from './dto/dashboard.dto';
|
|
import { UpdateProviderDto } from './dto/provider.dto';
|
|
import { CreatePackageDto, UpdatePackageDto } from './dto/package.dto';
|
|
import { CreatePromotionDto, UpdatePromotionDto, ValidatePromoCodeDto } from './dto/promotion.dto';
|
|
import { ApproveProductDto, RejectProductDto, ApproveReferralDto, RejectReferralDto } from './dto/moderation.dto';
|
|
|
|
interface AuthRequest extends Request {
|
|
user: { id: string; role: string };
|
|
}
|
|
|
|
@Controller('admin')
|
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
export class AdminController {
|
|
constructor(
|
|
private readonly dashboardService: AdminDashboardService,
|
|
private readonly providersService: AdminProvidersService,
|
|
private readonly packagesService: AdminPackagesService,
|
|
private readonly promotionsService: AdminPromotionsService,
|
|
private readonly moderationService: AdminModerationService,
|
|
) {}
|
|
|
|
// Dashboard Endpoints
|
|
|
|
@Get('dashboard')
|
|
@Roles(UserRole.VIEWER)
|
|
async getDashboard(@Query() query: DashboardQueryDto) {
|
|
const startDate = query.startDate ? new Date(query.startDate) : undefined;
|
|
const endDate = query.endDate ? new Date(query.endDate) : undefined;
|
|
|
|
const metrics = await this.dashboardService.getDashboardMetrics(startDate, endDate);
|
|
return { metrics };
|
|
}
|
|
|
|
@Get('dashboard/revenue-series')
|
|
@Roles(UserRole.ADMIN)
|
|
async getRevenueSeries(@Query() query: DashboardQueryDto) {
|
|
const now = new Date();
|
|
const startDate = query.startDate
|
|
? new Date(query.startDate)
|
|
: new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
const endDate = query.endDate ? new Date(query.endDate) : now;
|
|
const period = query.period || DashboardPeriod.DAY;
|
|
|
|
const series = await this.dashboardService.getRevenueSeries(startDate, endDate, period);
|
|
return { series };
|
|
}
|
|
|
|
// IA Providers Endpoints
|
|
|
|
@Get('providers')
|
|
@Roles(UserRole.ADMIN)
|
|
async getProviders() {
|
|
const providers = await this.providersService.findAll();
|
|
return { providers };
|
|
}
|
|
|
|
@Patch('providers/:id')
|
|
@Roles(UserRole.SUPER_ADMIN)
|
|
async updateProvider(
|
|
@Req() req: AuthRequest,
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdateProviderDto,
|
|
) {
|
|
const provider = await this.providersService.update(id, dto, req.user.id);
|
|
return {
|
|
message: 'Proveedor actualizado exitosamente',
|
|
provider,
|
|
};
|
|
}
|
|
|
|
// Credit Packages Endpoints
|
|
|
|
@Get('packages')
|
|
@Roles(UserRole.ADMIN)
|
|
async getPackages(@Query('includeInactive') includeInactive?: string) {
|
|
const packages = await this.packagesService.findAll(includeInactive === 'true');
|
|
return { packages };
|
|
}
|
|
|
|
@Post('packages')
|
|
@Roles(UserRole.ADMIN)
|
|
async createPackage(@Req() req: AuthRequest, @Body() dto: CreatePackageDto) {
|
|
const pkg = await this.packagesService.create(dto, req.user.id);
|
|
return {
|
|
message: 'Paquete creado exitosamente',
|
|
package: pkg,
|
|
};
|
|
}
|
|
|
|
@Patch('packages/:id')
|
|
@Roles(UserRole.ADMIN)
|
|
async updatePackage(
|
|
@Req() req: AuthRequest,
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdatePackageDto,
|
|
) {
|
|
const pkg = await this.packagesService.update(id, dto, req.user.id);
|
|
return {
|
|
message: 'Paquete actualizado exitosamente',
|
|
package: pkg,
|
|
};
|
|
}
|
|
|
|
// Promotions Endpoints
|
|
|
|
@Get('promotions')
|
|
@Roles(UserRole.ADMIN)
|
|
async getPromotions(@Query('includeExpired') includeExpired?: string) {
|
|
const promotions = await this.promotionsService.findAll(includeExpired === 'true');
|
|
return { promotions };
|
|
}
|
|
|
|
@Post('promotions')
|
|
@Roles(UserRole.ADMIN)
|
|
async createPromotion(@Req() req: AuthRequest, @Body() dto: CreatePromotionDto) {
|
|
const promotion = await this.promotionsService.create(dto, req.user.id);
|
|
return {
|
|
message: 'Promocion creada exitosamente',
|
|
promotion,
|
|
};
|
|
}
|
|
|
|
@Patch('promotions/:id')
|
|
@Roles(UserRole.ADMIN)
|
|
async updatePromotion(
|
|
@Req() req: AuthRequest,
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: UpdatePromotionDto,
|
|
) {
|
|
const promotion = await this.promotionsService.update(id, dto, req.user.id);
|
|
return {
|
|
message: 'Promocion actualizada exitosamente',
|
|
promotion,
|
|
};
|
|
}
|
|
|
|
@Post('promotions/validate')
|
|
@Roles(UserRole.VIEWER)
|
|
async validatePromoCode(@Body() dto: ValidatePromoCodeDto) {
|
|
return this.promotionsService.validateCode(dto.code, dto.packageId, dto.purchaseAmount);
|
|
}
|
|
|
|
// Product Moderation Endpoints
|
|
|
|
@Get('products/pending')
|
|
@Roles(UserRole.MODERATOR)
|
|
async getPendingProducts(
|
|
@Query('page') page?: string,
|
|
@Query('limit') limit?: string,
|
|
) {
|
|
return this.moderationService.getPendingProducts(
|
|
page ? parseInt(page, 10) : 1,
|
|
limit ? parseInt(limit, 10) : 20,
|
|
);
|
|
}
|
|
|
|
@Post('products/:id/approve')
|
|
@Roles(UserRole.MODERATOR)
|
|
async approveProduct(
|
|
@Req() req: AuthRequest,
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: ApproveProductDto,
|
|
) {
|
|
const product = await this.moderationService.approveProduct(id, dto, req.user.id);
|
|
return {
|
|
message: 'Producto aprobado exitosamente',
|
|
product,
|
|
};
|
|
}
|
|
|
|
@Post('products/:id/reject')
|
|
@Roles(UserRole.MODERATOR)
|
|
async rejectProduct(
|
|
@Req() req: AuthRequest,
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: RejectProductDto,
|
|
) {
|
|
const product = await this.moderationService.rejectProduct(id, dto, req.user.id);
|
|
return {
|
|
message: 'Producto rechazado',
|
|
product,
|
|
};
|
|
}
|
|
|
|
// Referral Fraud Moderation Endpoints
|
|
|
|
@Get('referrals/fraud-holds')
|
|
@Roles(UserRole.MODERATOR)
|
|
async getFraudHoldReferrals(
|
|
@Query('page') page?: string,
|
|
@Query('limit') limit?: string,
|
|
) {
|
|
return this.moderationService.getFraudHoldReferrals(
|
|
page ? parseInt(page, 10) : 1,
|
|
limit ? parseInt(limit, 10) : 20,
|
|
);
|
|
}
|
|
|
|
@Post('referrals/:id/approve')
|
|
@Roles(UserRole.MODERATOR)
|
|
async approveReferral(
|
|
@Req() req: AuthRequest,
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: ApproveReferralDto,
|
|
) {
|
|
const referral = await this.moderationService.approveReferral(id, dto, req.user.id);
|
|
return {
|
|
message: 'Referido aprobado exitosamente',
|
|
referral,
|
|
};
|
|
}
|
|
|
|
@Post('referrals/:id/reject')
|
|
@Roles(UserRole.MODERATOR)
|
|
async rejectReferral(
|
|
@Req() req: AuthRequest,
|
|
@Param('id', ParseUUIDPipe) id: string,
|
|
@Body() dto: RejectReferralDto,
|
|
) {
|
|
const referral = await this.moderationService.rejectReferral(id, dto, req.user.id);
|
|
return {
|
|
message: 'Referido rechazado por fraude',
|
|
referral,
|
|
};
|
|
}
|
|
}
|