Files
goodgo-platform/apps/api/src/modules/search/presentation/controllers/saved-search.controller.ts
Ho Ngoc Hai d4e100a00c feat(api): add price history, Stringee SMS, Zalo OA, WebSocket notifications, and feature-listing command
- Add PriceHistory model + migration, price-changed domain event, and event handler
- Add GetPriceHistory query handler and controller endpoint
- Implement StringeeSmsService and ZaloOaService with unit tests
- Add Zalo ZNS templates for Vietnamese notification messages
- Add WebSocket notification gateway for real-time push
- Add FeatureListingCommand for promoted listings
- Apply remaining consistent-type-imports lint fixes across API modules

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 05:15:04 +07:00

127 lines
5.0 KiB
TypeScript

import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { type CommandBus, type QueryBus } from '@nestjs/cqrs';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiParam,
} from '@nestjs/swagger';
import { type JwtPayload, CurrentUser, JwtAuthGuard } from '@modules/auth';
import { CreateSavedSearchCommand } from '../../application/commands/create-saved-search/create-saved-search.command';
import { type CreateSavedSearchResult } from '../../application/commands/create-saved-search/create-saved-search.handler';
import { DeleteSavedSearchCommand } from '../../application/commands/delete-saved-search/delete-saved-search.command';
import { UpdateSavedSearchCommand } from '../../application/commands/update-saved-search/update-saved-search.command';
import { type UpdateSavedSearchResult } from '../../application/commands/update-saved-search/update-saved-search.handler';
import { type SavedSearchDetail } from '../../application/queries/get-saved-search/get-saved-search.handler';
import { GetSavedSearchQuery } from '../../application/queries/get-saved-search/get-saved-search.query';
import { type SavedSearchListResult } from '../../application/queries/get-saved-searches/get-saved-searches.handler';
import { GetSavedSearchesQuery } from '../../application/queries/get-saved-searches/get-saved-searches.query';
import { type CreateSavedSearchDto, type UpdateSavedSearchDto, type SavedSearchListDto } from '../dto/saved-search.dto';
@ApiTags('saved-searches')
@ApiBearerAuth('JWT')
@UseGuards(JwtAuthGuard)
@Controller('saved-searches')
export class SavedSearchController {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus,
) {}
@Post()
@ApiOperation({ summary: 'Lưu tìm kiếm', description: 'Lưu bộ lọc tìm kiếm để nhận thông báo khi có kết quả mới' })
@ApiResponse({ status: 201, description: 'Tìm kiếm đã được lưu' })
@ApiResponse({ status: 400, description: 'Dữ liệu không hợp lệ' })
@ApiResponse({ status: 401, description: 'Chưa đăng nhập' })
@ApiResponse({ status: 403, description: 'Đã đạt giới hạn gói đăng ký' })
async create(
@Body() dto: CreateSavedSearchDto,
@CurrentUser() user: JwtPayload,
): Promise<CreateSavedSearchResult> {
return this.commandBus.execute(
new CreateSavedSearchCommand(
user.sub,
dto.name,
dto.filters,
dto.alertEnabled ?? true,
),
);
}
@Get()
@ApiOperation({ summary: 'Danh sách tìm kiếm đã lưu', description: 'Lấy danh sách tìm kiếm đã lưu của người dùng' })
@ApiResponse({ status: 200, description: 'Danh sách tìm kiếm đã lưu' })
@ApiResponse({ status: 401, description: 'Chưa đăng nhập' })
async list(
@Query() dto: SavedSearchListDto,
@CurrentUser() user: JwtPayload,
): Promise<SavedSearchListResult> {
return this.queryBus.execute(
new GetSavedSearchesQuery(user.sub, dto.page ?? 1, dto.limit ?? 20),
);
}
@Get(':id')
@ApiOperation({ summary: 'Chi tiết tìm kiếm đã lưu', description: 'Lấy chi tiết một tìm kiếm đã lưu' })
@ApiParam({ name: 'id', description: 'ID tìm kiếm đã lưu' })
@ApiResponse({ status: 200, description: 'Chi tiết tìm kiếm đã lưu' })
@ApiResponse({ status: 401, description: 'Chưa đăng nhập' })
@ApiResponse({ status: 404, description: 'Không tìm thấy' })
async getById(
@Param('id') id: string,
@CurrentUser() user: JwtPayload,
): Promise<SavedSearchDetail> {
return this.queryBus.execute(
new GetSavedSearchQuery(id, user.sub),
);
}
@Patch(':id')
@ApiOperation({ summary: 'Cập nhật tìm kiếm đã lưu', description: 'Cập nhật tên, bộ lọc hoặc trạng thái thông báo' })
@ApiParam({ name: 'id', description: 'ID tìm kiếm đã lưu' })
@ApiResponse({ status: 200, description: 'Đã cập nhật' })
@ApiResponse({ status: 401, description: 'Chưa đăng nhập' })
@ApiResponse({ status: 404, description: 'Không tìm thấy' })
async update(
@Param('id') id: string,
@Body() dto: UpdateSavedSearchDto,
@CurrentUser() user: JwtPayload,
): Promise<UpdateSavedSearchResult> {
return this.commandBus.execute(
new UpdateSavedSearchCommand(
id,
user.sub,
dto.name,
dto.filters,
dto.alertEnabled,
),
);
}
@Delete(':id')
@ApiOperation({ summary: 'Xóa tìm kiếm đã lưu', description: 'Xóa một tìm kiếm đã lưu' })
@ApiParam({ name: 'id', description: 'ID tìm kiếm đã lưu' })
@ApiResponse({ status: 200, description: 'Đã xóa' })
@ApiResponse({ status: 401, description: 'Chưa đăng nhập' })
@ApiResponse({ status: 404, description: 'Không tìm thấy' })
async delete(
@Param('id') id: string,
@CurrentUser() user: JwtPayload,
): Promise<{ deleted: boolean }> {
return this.commandBus.execute(
new DeleteSavedSearchCommand(id, user.sub),
);
}
}