Files
goodgo-platform/apps/api/src/modules/leads/presentation/controllers/leads.controller.ts
Ho Ngoc Hai 312532b1cb fix(api): resolve NestJS DI + ValidationPipe bugs from type-only imports
- Remove `type` modifier from imports used as DI constructor params
  across ~235 files (@Injectable, @Controller, @Module, @Catch,
  @CommandHandler, @QueryHandler, @EventsHandler, @WebSocketGateway).
  TypeScript emitDecoratorMetadata strips type-only imports, leaving
  Reflect.metadata with Function placeholder and breaking Nest DI.
- Fix controllers: DTOs used with @Body/@Query/@Param must be runtime
  imports so ValidationPipe can whitelist properties. Previously
  returned 400 "property X should not exist" on every request.
- Register ProjectsModule in AppModule (was defined but never wired).
- Add approve()/reject() methods to TransferListingEntity referenced by
  ModerateTransferListingHandler.
- Export BankTransferConfirmedEvent from payments barrel for
  subscription activation handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:50:30 +07:00

127 lines
4.3 KiB
TypeScript

import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiParam,
} from '@nestjs/swagger';
import { type JwtPayload, CurrentUser, JwtAuthGuard, RolesGuard, Roles } from '@modules/auth';
import { CreateLeadCommand } from '../../application/commands/create-lead/create-lead.command';
import { type CreateLeadResult } from '../../application/commands/create-lead/create-lead.handler';
import { DeleteLeadCommand } from '../../application/commands/delete-lead/delete-lead.command';
import { UpdateLeadStatusCommand } from '../../application/commands/update-lead-status/update-lead-status.command';
import { GetLeadStatsQuery } from '../../application/queries/get-lead-stats/get-lead-stats.query';
import { GetLeadsByAgentQuery } from '../../application/queries/get-leads-by-agent/get-leads-by-agent.query';
import { type LeadReadDto } from '../../domain/repositories/lead-read.dto';
import { type LeadStatsData, type PaginatedResult } from '../../domain/repositories/lead.repository';
import { CreateLeadDto } from '../dto/create-lead.dto';
import { ListLeadsDto } from '../dto/list-leads.dto';
import { UpdateLeadStatusDto } from '../dto/update-lead-status.dto';
@ApiTags('leads')
@ApiBearerAuth('JWT')
@Controller('leads')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('AGENT')
export class LeadsController {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus,
) {}
@ApiOperation({ summary: 'Tạo lead mới' })
@ApiResponse({ status: 201, description: 'Lead đã được tạo thành công' })
@ApiResponse({ status: 400, description: 'Lỗi validation' })
@ApiResponse({ status: 401, description: 'Chưa xác thực' })
@ApiResponse({ status: 403, description: 'Không có quyền' })
@Post()
async createLead(
@Body() dto: CreateLeadDto,
@CurrentUser() user: JwtPayload,
): Promise<CreateLeadResult> {
return this.commandBus.execute(
new CreateLeadCommand(
user.sub,
dto.name,
dto.phone,
dto.email ?? null,
dto.source,
dto.score ?? null,
dto.notes ?? null,
),
);
}
@ApiOperation({ summary: 'Danh sách lead của agent' })
@ApiResponse({ status: 200, description: 'Danh sách lead phân trang' })
@Get()
async getLeads(
@Query() dto: ListLeadsDto,
@CurrentUser() user: JwtPayload,
): Promise<PaginatedResult<LeadReadDto>> {
return this.queryBus.execute(
new GetLeadsByAgentQuery(
user.sub,
dto.status ?? null,
dto.page ?? 1,
dto.limit ?? 20,
),
);
}
@ApiOperation({ summary: 'Thống kê lead của agent' })
@ApiResponse({ status: 200, description: 'Thống kê lead' })
@Get('stats')
async getStats(
@CurrentUser() user: JwtPayload,
): Promise<LeadStatsData> {
return this.queryBus.execute(
new GetLeadStatsQuery(user.sub),
);
}
@ApiOperation({ summary: 'Cập nhật trạng thái lead' })
@ApiParam({ name: 'id', description: 'Lead ID' })
@ApiResponse({ status: 200, description: 'Trạng thái đã được cập nhật' })
@ApiResponse({ status: 400, description: 'Chuyển trạng thái không hợp lệ' })
@ApiResponse({ status: 403, description: 'Không có quyền' })
@ApiResponse({ status: 404, description: 'Không tìm thấy lead' })
@Patch(':id/status')
async updateStatus(
@Param('id') id: string,
@Body() dto: UpdateLeadStatusDto,
@CurrentUser() user: JwtPayload,
): Promise<{ updated: boolean }> {
await this.commandBus.execute(
new UpdateLeadStatusCommand(id, user.sub, dto.status),
);
return { updated: true };
}
@ApiOperation({ summary: 'Xóa lead' })
@ApiParam({ name: 'id', description: 'Lead ID' })
@ApiResponse({ status: 200, description: 'Lead đã được xóa' })
@ApiResponse({ status: 403, description: 'Không có quyền' })
@ApiResponse({ status: 404, description: 'Không tìm thấy lead' })
@Delete(':id')
async deleteLead(
@Param('id') id: string,
@CurrentUser() user: JwtPayload,
): Promise<{ deleted: boolean }> {
await this.commandBus.execute(new DeleteLeadCommand(id, user.sub));
return { deleted: true };
}
}