110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
/**
|
|
* MSC (Mediterranean Shipping Company) Connector
|
|
*
|
|
* Implements CarrierConnectorPort for MSC API integration
|
|
*/
|
|
|
|
import { Injectable, Logger } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import {
|
|
CarrierConnectorPort,
|
|
CarrierRateSearchInput,
|
|
CarrierAvailabilityInput
|
|
} from '../../../domain/ports/out/carrier-connector.port';
|
|
import { RateQuote } from '../../../domain/entities/rate-quote.entity';
|
|
import { BaseCarrierConnector, CarrierConfig } from '../base-carrier.connector';
|
|
import { MSCRequestMapper } from './msc.mapper';
|
|
|
|
@Injectable()
|
|
export class MSCConnectorAdapter
|
|
extends BaseCarrierConnector
|
|
implements CarrierConnectorPort
|
|
{
|
|
private readonly apiUrl: string;
|
|
private readonly apiKey: string;
|
|
|
|
constructor(
|
|
private readonly configService: ConfigService,
|
|
private readonly requestMapper: MSCRequestMapper,
|
|
) {
|
|
const config: CarrierConfig = {
|
|
name: 'MSC',
|
|
code: 'MSCU',
|
|
baseUrl: configService.get<string>('MSC_API_URL', 'https://api.msc.com/v1'),
|
|
timeout: 5000,
|
|
maxRetries: 3,
|
|
circuitBreakerThreshold: 50,
|
|
circuitBreakerTimeout: 30000,
|
|
};
|
|
super(config);
|
|
this.apiUrl = config.baseUrl;
|
|
this.apiKey = this.configService.get<string>('MSC_API_KEY', '');
|
|
}
|
|
|
|
async searchRates(input: CarrierRateSearchInput): Promise<RateQuote[]> {
|
|
this.logger.log(`Searching MSC rates: ${input.origin} -> ${input.destination}`);
|
|
|
|
try {
|
|
// Map internal format to MSC API format
|
|
const mscRequest = this.requestMapper.toMSCRequest(input);
|
|
|
|
// Make API call with circuit breaker
|
|
const response = await this.makeRequest({
|
|
url: `${this.apiUrl}/rates/search`,
|
|
method: 'POST',
|
|
data: mscRequest,
|
|
headers: {
|
|
'X-API-Key': this.apiKey,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
// Map MSC response to domain entities
|
|
const rateQuotes = this.requestMapper.fromMSCResponse(response.data, input);
|
|
|
|
this.logger.log(`Found ${rateQuotes.length} MSC rates`);
|
|
return rateQuotes;
|
|
} catch (error: any) {
|
|
this.logger.error(`MSC API error: ${error?.message || 'Unknown error'}`);
|
|
|
|
// Handle specific MSC error codes
|
|
if (error?.response?.status === 404) {
|
|
this.logger.warn('No MSC rates found for this route');
|
|
return [];
|
|
}
|
|
|
|
if (error?.response?.status === 429) {
|
|
this.logger.error('MSC rate limit exceeded');
|
|
throw new Error('MSC_RATE_LIMIT_EXCEEDED');
|
|
}
|
|
|
|
// Return empty array on error (fail gracefully)
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async checkAvailability(input: CarrierAvailabilityInput): Promise<number> {
|
|
try {
|
|
const response = await this.makeRequest({
|
|
url: `${this.apiUrl}/availability/check`,
|
|
method: 'POST',
|
|
data: {
|
|
origin: input.origin,
|
|
destination: input.destination,
|
|
departure_date: input.departureDate,
|
|
container_type: input.containerType,
|
|
quantity: input.quantity,
|
|
},
|
|
headers: {
|
|
'X-API-Key': this.apiKey,
|
|
},
|
|
});
|
|
|
|
return (response.data as any).available_slots || 0;
|
|
} catch (error: any) {
|
|
this.logger.error(`MSC availability check error: ${error?.message || 'Unknown error'}`);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|