111 lines
3.5 KiB
TypeScript
111 lines
3.5 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(),
|
|
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;
|
|
}
|
|
}
|
|
}
|