xpeditis2.0/apps/backend/src/application/controllers/rates.controller.ts
2025-10-20 12:30:08 +02:00

120 lines
3.5 KiB
TypeScript

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<RateSearchResponseDto> {
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;
}
}
}