/** * PortSearchService * * Domain service for port search and autocomplete * * Business Rules: * - Fuzzy search on port name, city, and code * - Return top 10 results by default * - Support country filtering */ import { Port } from '../entities/port.entity'; import { GetPortsPort, PortSearchInput, PortSearchOutput, GetPortInput, } from '@domain/ports/in/get-ports.port'; import { PortRepository } from '@domain/ports/out/port.repository'; import { PortNotFoundException } from '../exceptions/port-not-found.exception'; export class PortSearchService implements GetPortsPort { private static readonly DEFAULT_LIMIT = 10; constructor(private readonly portRepository: PortRepository) {} async search(input: PortSearchInput): Promise { const limit = input.limit || PortSearchService.DEFAULT_LIMIT; const query = input.query.trim(); if (query.length === 0) { return { ports: [], totalMatches: 0, }; } // Search using repository fuzzy search const ports = await this.portRepository.search(query, limit, input.countryFilter); return { ports, totalMatches: ports.length, }; } async getByCode(input: GetPortInput): Promise { const port = await this.portRepository.findByCode(input.portCode); if (!port) { throw new PortNotFoundException(input.portCode); } return port; } async getByCodes(portCodes: string[]): Promise { const ports = await this.portRepository.findByCodes(portCodes); // Check if all ports were found const foundCodes = ports.map(p => p.code); const missingCodes = portCodes.filter(code => !foundCodes.includes(code)); if (missingCodes.length > 0) { throw new PortNotFoundException(missingCodes[0]); } return ports; } }