/** * Maersk Connector * * Implementation of CarrierConnectorPort for Maersk API */ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { v4 as uuidv4 } from 'uuid'; import { BaseCarrierConnector, CarrierConfig } from '../base-carrier.connector'; import { CarrierRateSearchInput, CarrierAvailabilityInput, } from '../../../domain/ports/out/carrier-connector.port'; import { RateQuote } from '../../../domain/entities/rate-quote.entity'; import { MaerskRequestMapper } from './maersk-request.mapper'; import { MaerskResponseMapper } from './maersk-response.mapper'; import { MaerskRateSearchRequest, MaerskRateSearchResponse } from './maersk.types'; @Injectable() export class MaerskConnector extends BaseCarrierConnector { constructor(private readonly configService: ConfigService) { const config: CarrierConfig = { name: 'Maersk', code: 'MAERSK', baseUrl: configService.get('MAERSK_API_BASE_URL', 'https://api.maersk.com/v1'), timeout: 5000, // 5 seconds maxRetries: 2, circuitBreakerThreshold: 50, // Open circuit after 50% failures circuitBreakerTimeout: 30000, // Wait 30s before half-open }; super(config); } async searchRates(input: CarrierRateSearchInput): Promise { try { // Map domain input to Maersk API format const maerskRequest = MaerskRequestMapper.toMaerskRateSearchRequest(input); // Make API request with circuit breaker const response = await this.requestWithCircuitBreaker({ method: 'POST', url: '/rates/search', data: maerskRequest, headers: { 'API-Key': this.configService.get('MAERSK_API_KEY'), }, }); // Map Maersk API response to domain entities const rateQuotes = MaerskResponseMapper.toRateQuotes( response.data, input.origin, input.destination ); this.logger.log(`Found ${rateQuotes.length} rate quotes from Maersk`); return rateQuotes; } catch (error: any) { this.logger.error(`Error searching Maersk rates: ${error?.message || 'Unknown error'}`); // Return empty array instead of throwing - allows other carriers to succeed return []; } } async checkAvailability(input: CarrierAvailabilityInput): Promise { try { const response = await this.requestWithCircuitBreaker<{ availability: number }>({ method: 'POST', url: '/availability/check', data: { origin: input.origin, destination: input.destination, containerType: input.containerType, departureDate: input.departureDate?.toISOString() || input.startDate.toISOString(), quantity: input.quantity, }, headers: { 'API-Key': this.configService.get('MAERSK_API_KEY'), }, }); return response.data.availability; } catch (error: any) { this.logger.error(`Error checking Maersk availability: ${error?.message || 'Unknown error'}`); return 0; } } /** * Override health check to use Maersk-specific endpoint */ async healthCheck(): Promise { try { await this.requestWithCircuitBreaker({ method: 'GET', url: '/status', timeout: 3000, headers: { 'API-Key': this.configService.get('MAERSK_API_KEY'), }, }); return true; } catch (error: any) { this.logger.warn(`Maersk health check failed: ${error?.message || 'Unknown error'}`); return false; } } }