99 lines
2.8 KiB
TypeScript
99 lines
2.8 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Query,
|
|
HttpCode,
|
|
HttpStatus,
|
|
Logger,
|
|
UsePipes,
|
|
ValidationPipe,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import {
|
|
ApiTags,
|
|
ApiOperation,
|
|
ApiResponse,
|
|
ApiBadRequestResponse,
|
|
ApiInternalServerErrorResponse,
|
|
ApiBearerAuth,
|
|
} from '@nestjs/swagger';
|
|
import { PortSearchRequestDto, PortSearchResponseDto } from '../dto/port.dto';
|
|
import { PortMapper } from '../mappers/port.mapper';
|
|
import { PortSearchService } from '@domain/services/port-search.service';
|
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
|
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
|
|
|
|
@ApiTags('Ports')
|
|
@Controller('ports')
|
|
@ApiBearerAuth()
|
|
export class PortsController {
|
|
private readonly logger = new Logger(PortsController.name);
|
|
|
|
constructor(private readonly portSearchService: PortSearchService) {}
|
|
|
|
@Get('search')
|
|
@UseGuards(JwtAuthGuard)
|
|
@HttpCode(HttpStatus.OK)
|
|
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
|
@ApiOperation({
|
|
summary: 'Search ports (autocomplete)',
|
|
description:
|
|
'Search for maritime ports by name, city, or UN/LOCODE code. Returns up to 50 results ordered by relevance. Requires authentication.',
|
|
})
|
|
@ApiResponse({
|
|
status: HttpStatus.OK,
|
|
description: 'Port search completed successfully',
|
|
type: PortSearchResponseDto,
|
|
})
|
|
@ApiResponse({
|
|
status: 401,
|
|
description: 'Unauthorized - missing or invalid token',
|
|
})
|
|
@ApiBadRequestResponse({
|
|
description: 'Invalid request parameters',
|
|
schema: {
|
|
example: {
|
|
statusCode: 400,
|
|
message: ['query must be a string'],
|
|
error: 'Bad Request',
|
|
},
|
|
},
|
|
})
|
|
@ApiInternalServerErrorResponse({
|
|
description: 'Internal server error',
|
|
})
|
|
async searchPorts(
|
|
@Query() dto: PortSearchRequestDto,
|
|
@CurrentUser() user: UserPayload
|
|
): Promise<PortSearchResponseDto> {
|
|
const startTime = Date.now();
|
|
this.logger.log(
|
|
`[User: ${user.email}] Searching ports: query="${dto.query}", limit=${dto.limit || 10}, country=${dto.countryFilter || 'all'}`
|
|
);
|
|
|
|
try {
|
|
// Call domain service
|
|
const result = await this.portSearchService.search({
|
|
query: dto.query,
|
|
limit: dto.limit,
|
|
countryFilter: dto.countryFilter,
|
|
});
|
|
|
|
const duration = Date.now() - startTime;
|
|
this.logger.log(
|
|
`[User: ${user.email}] Port search completed: ${result.totalMatches} results in ${duration}ms`
|
|
);
|
|
|
|
// Map to response DTO
|
|
return PortMapper.toSearchResponseDto(result.ports, result.totalMatches);
|
|
} catch (error: any) {
|
|
const duration = Date.now() - startTime;
|
|
this.logger.error(
|
|
`[User: ${user.email}] Port search failed after ${duration}ms: ${error?.message || 'Unknown error'}`,
|
|
error?.stack
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|