268 lines
8.3 KiB
JavaScript
268 lines
8.3 KiB
JavaScript
'use strict';
|
|
|
|
const logger = require('../helpers/logger');
|
|
const pino = logger.child('partner_sync_service');
|
|
|
|
const { AppParamError, AppError } = require('../helpers/app_error');
|
|
/**
|
|
* Simple Partner Sync Service
|
|
* Handles synchronization of jobs with partner systems using environment-based configuration
|
|
*/
|
|
|
|
const { HealthStatus, PartnerOperations, AssignStatus, Errors } = require('../helpers/constants'),
|
|
partnerServiceFactory = require('./partner_service_factory'),
|
|
{ JobStatus } = require('../helpers/job_constants'),
|
|
{ runWithSessionOrTransaction } = require('../helpers/mongo_enhanced'),
|
|
JobAssign = require('../model/job_assign'),
|
|
jobUtil = require('../helpers/job_util');
|
|
|
|
class PartnerSyncService {
|
|
constructor() {
|
|
this.activeServices = new Map();
|
|
this.initializeServices();
|
|
}
|
|
|
|
initializeServices() {
|
|
// Initialize all supported partner services
|
|
const supportedPartners = partnerServiceFactory.getSupportedPartners();
|
|
|
|
supportedPartners.forEach(partnerCode => {
|
|
try {
|
|
const service = partnerServiceFactory.getService(partnerCode);
|
|
this.activeServices.set(partnerCode, service);
|
|
pino.info(`Partner service initialized: ${partnerCode}`);
|
|
} catch (error) {
|
|
pino.warn({ err: error }, `Failed to initialize partner service: ${partnerCode}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Upload job data to partner system
|
|
* @param {string} assignId - Job assignment ID
|
|
* @param {object} options - Options object
|
|
* @param {object} options.session - MongoDB session for atomic transactions
|
|
* @returns {Promise<object>} Upload result
|
|
*/
|
|
async uploadJobToPartner(assignId, options = {}) {
|
|
try {
|
|
const assignments = await JobAssign.findByIdWithPartnerInfo(assignId);
|
|
const assignment = Array.isArray(assignments) ? assignments[0] : assignments; if (!assignment) {
|
|
logger.logError(new Error('Assignment not found'), {
|
|
operation: PartnerOperations.UPLOAD_JOB,
|
|
assignmentId: assignId
|
|
});
|
|
AppParamError.throw(Errors.INVALID_ASSIGNMENT);
|
|
}
|
|
|
|
// Check if user (vehicle) has partner integration
|
|
if (!assignment.hasPartnerIntegration()) {
|
|
return { success: false, message: 'No partner integration, no upload needed' };
|
|
}
|
|
|
|
const partnerCode = assignment.getPartnerCode();
|
|
const partnerService = this.activeServices.get(partnerCode);
|
|
|
|
if (!partnerService) {
|
|
logger.logError(new Error('Partner service not available'), {
|
|
operation: PartnerOperations.UPLOAD_JOB,
|
|
assignmentId: assignId,
|
|
partnerCode
|
|
});
|
|
AppError.throw(Errors.PARTNER_SERVICE_UNAVAILABLE);
|
|
}
|
|
|
|
// Let the partner service decide how to format the job data
|
|
const result = await partnerService.uploadJobDataToAircraft(assignment);
|
|
|
|
logger.logPartnerOperation(
|
|
PartnerOperations.UPLOAD_JOB,
|
|
partnerCode,
|
|
result.success,
|
|
{
|
|
assignmentId: assignId,
|
|
jobId: assignment.job._id,
|
|
partnerAircraftId: assignment.getPartnerAircraftId(),
|
|
partnerJobId: result.externalJobId
|
|
}
|
|
);
|
|
|
|
// If upload was successful, update assignment status and write job log atomically
|
|
if (result.success) {
|
|
// Use runWithSessionOrTransaction to properly handle both provided sessions and new transactions
|
|
await runWithSessionOrTransaction(async (session) => {
|
|
// Update assignment status to 'uploaded'
|
|
await jobUtil.updateAssignStatusById(assignId, AssignStatus.UPLOADED, {
|
|
externalJobId: result.externalJobId,
|
|
date: new Date(),
|
|
}, session);
|
|
|
|
// Write Uploaded job log entry, job status to Downloaded
|
|
await jobUtil.writeJobLog(assignment.job._id, AssignStatus.UPLOADED, assignment.user._id, {
|
|
updateJobStatus: true,
|
|
jobStatusValue: JobStatus.DOWNLOADED,
|
|
session: session
|
|
});
|
|
}, options.session);
|
|
}
|
|
|
|
return {
|
|
success: result.success,
|
|
message: result.message,
|
|
externalJobId: result.externalJobId,
|
|
partnerCode,
|
|
partnerAircraftId: assignment.getPartnerAircraftId()
|
|
};
|
|
|
|
} catch (error) {
|
|
logger.logError(error, {
|
|
operation: PartnerOperations.UPLOAD_JOB,
|
|
assignmentId: assignId
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check health of all partner services
|
|
* @returns {Promise<object>} Health status
|
|
*/
|
|
async healthCheck() {
|
|
const results = {
|
|
overall: HealthStatus.HEALTHY,
|
|
partners: {}
|
|
};
|
|
|
|
for (const [partnerCode, service] of this.activeServices) {
|
|
try {
|
|
// Use the service's healthCheck method which calls /IsAlive for SatLoc
|
|
const health = await service.healthCheck();
|
|
results.partners[partnerCode] = {
|
|
status: health.isAlive ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY,
|
|
isAlive: health.isAlive,
|
|
timestamp: health.timestamp,
|
|
responseTime: health.responseTime,
|
|
error: health.error
|
|
};
|
|
} catch (error) {
|
|
results.partners[partnerCode] = {
|
|
status: HealthStatus.UNHEALTHY,
|
|
isAlive: false,
|
|
error: error.message,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
results.overall = HealthStatus.DEGRADED;
|
|
}
|
|
}
|
|
|
|
if (Object.values(results.partners).some(p => p.status === HealthStatus.UNHEALTHY)) {
|
|
results.overall = HealthStatus.UNHEALTHY;
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Get available partner services
|
|
* @returns {array} Available partner codes
|
|
*/
|
|
getAvailablePartners() {
|
|
return Array.from(this.activeServices.keys());
|
|
}
|
|
|
|
/**
|
|
* Check if a partner service is available
|
|
* @param {string} partnerCode - Partner code, must be uppercase or it will be converted to Uppercase while looking it up
|
|
* @returns {boolean} Whether service is available
|
|
*/
|
|
isPartnerAvailable(partnerCode) {
|
|
return this.activeServices.has(String(partnerCode).toUpperCase());
|
|
}
|
|
|
|
/**
|
|
* Check health of a specific partner API
|
|
* @param {string} partnerCode - Partner code to check
|
|
* @returns {Promise<boolean>} Whether API is live
|
|
*/
|
|
async checkPartnerAPIHealth(partnerCode) {
|
|
try {
|
|
if (!this.isPartnerAvailable(partnerCode)) {
|
|
return false;
|
|
}
|
|
|
|
// Quick health check for the specific partner
|
|
const partnerService = this.activeServices.get(partnerCode);
|
|
if (!partnerService || !partnerService.healthCheck) {
|
|
return false;
|
|
}
|
|
|
|
const healthResult = await Promise.race([
|
|
partnerService.healthCheck(),
|
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
|
|
]);
|
|
|
|
return healthResult && healthResult.healthy !== false;
|
|
} catch (error) {
|
|
pino.debug({ err: error }, `Partner API health check failed for ${partnerCode}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get aircraft list from a partner system
|
|
* @param {string} partnerCode - Partner code (e.g., 'SATLOC')
|
|
* @param {string} customerId - Customer ID
|
|
* @returns {Promise<object>} Aircraft list response
|
|
*/
|
|
async getPartnerAircraftList(partnerCode, customerId) {
|
|
try {
|
|
if (!this.isPartnerAvailable(partnerCode)) {
|
|
return {
|
|
success: false,
|
|
error: `Invalid partner code: ${partnerCode}`,
|
|
partnerCode
|
|
};
|
|
}
|
|
|
|
const partnerService = this.activeServices.get(partnerCode);
|
|
if (!partnerService || typeof partnerService.getAircraftList !== 'function') {
|
|
return {
|
|
success: false,
|
|
error: `Aircraft list method not available for partner: ${partnerCode}`,
|
|
partnerCode
|
|
};
|
|
}
|
|
|
|
// Call partner-specific aircraft list method
|
|
const result = await partnerService.getAircraftList(customerId);
|
|
|
|
logger.logPartnerOperation(
|
|
PartnerOperations.GET_AIRCRAFT_LIST,
|
|
partnerCode,
|
|
result.success,
|
|
{
|
|
customerId,
|
|
aircraftCount: result.aircraft?.length || 0
|
|
}
|
|
);
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
logger.logError(error, {
|
|
operation: PartnerOperations.GET_AIRCRAFT_LIST,
|
|
partnerCode,
|
|
customerId
|
|
});
|
|
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
partnerCode
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new PartnerSyncService();
|