xpeditis2.0/apps/backend/src/infrastructure/carriers/maersk/maersk.connector.ts
2025-10-27 20:54:01 +01:00

111 lines
3.6 KiB
TypeScript

/**
* 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<string>('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<RateQuote[]> {
try {
// Map domain input to Maersk API format
const maerskRequest = MaerskRequestMapper.toMaerskRateSearchRequest(input);
// Make API request with circuit breaker
const response = await this.requestWithCircuitBreaker<MaerskRateSearchResponse>({
method: 'POST',
url: '/rates/search',
data: maerskRequest,
headers: {
'API-Key': this.configService.get<string>('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<number> {
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<string>('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<boolean> {
try {
await this.requestWithCircuitBreaker({
method: 'GET',
url: '/status',
timeout: 3000,
headers: {
'API-Key': this.configService.get<string>('MAERSK_API_KEY'),
},
});
return true;
} catch (error: any) {
this.logger.warn(`Maersk health check failed: ${error?.message || 'Unknown error'}`);
return false;
}
}
}