- 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>
127 lines
4.3 KiB
TypeScript
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 };
|
|
}
|
|
}
|