import { Controller, Post, Body, HttpCode, HttpStatus, Logger, UsePipes, ValidationPipe, UseGuards, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBadRequestResponse, ApiInternalServerErrorResponse, ApiBearerAuth, } from '@nestjs/swagger'; import { RateSearchRequestDto, RateSearchResponseDto } from '../dto'; import { RateQuoteMapper } from '../mappers'; import { RateSearchService } from '../../domain/services/rate-search.service'; import { JwtAuthGuard } from '../guards/jwt-auth.guard'; import { CurrentUser, UserPayload } from '../decorators/current-user.decorator'; @ApiTags('Rates') @Controller('api/v1/rates') @ApiBearerAuth() export class RatesController { private readonly logger = new Logger(RatesController.name); constructor(private readonly rateSearchService: RateSearchService) {} @Post('search') @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) @ApiOperation({ summary: 'Search shipping rates', description: 'Search for available shipping rates from multiple carriers. Results are cached for 15 minutes. Requires authentication.', }) @ApiResponse({ status: HttpStatus.OK, description: 'Rate search completed successfully', type: RateSearchResponseDto, }) @ApiResponse({ status: 401, description: 'Unauthorized - missing or invalid token', }) @ApiBadRequestResponse({ description: 'Invalid request parameters', schema: { example: { statusCode: 400, message: ['Origin must be a valid 5-character UN/LOCODE (e.g., NLRTM)'], error: 'Bad Request', }, }, }) @ApiInternalServerErrorResponse({ description: 'Internal server error', }) async searchRates( @Body() dto: RateSearchRequestDto, @CurrentUser() user: UserPayload, ): Promise { const startTime = Date.now(); this.logger.log( `[User: ${user.email}] Searching rates: ${dto.origin} → ${dto.destination}, ${dto.containerType}`, ); try { // Convert DTO to domain input const searchInput = { origin: dto.origin, destination: dto.destination, containerType: dto.containerType, mode: dto.mode, departureDate: new Date(dto.departureDate), quantity: dto.quantity, weight: dto.weight, volume: dto.volume, isHazmat: dto.isHazmat, imoClass: dto.imoClass, }; // Execute search const result = await this.rateSearchService.execute(searchInput); // Convert domain entities to DTOs const quoteDtos = RateQuoteMapper.toDtoArray(result.quotes); const responseTimeMs = Date.now() - startTime; this.logger.log( `Rate search completed: ${quoteDtos.length} quotes, ${responseTimeMs}ms`, ); return { quotes: quoteDtos, count: quoteDtos.length, origin: dto.origin, destination: dto.destination, departureDate: dto.departureDate, containerType: dto.containerType, mode: dto.mode, fromCache: false, // TODO: Implement cache detection responseTimeMs, }; } catch (error: any) { this.logger.error( `Rate search failed: ${error?.message || 'Unknown error'}`, error?.stack, ); throw error; } } }