575 lines
18 KiB
JavaScript
575 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
const env = require('../helpers/env');
|
|
|
|
const { Partner, PartnerSystemUser } = require('../model/partner'),
|
|
User = require('../model/user'),
|
|
{ AppParamError, AppError, AppAuthError } = require('../helpers/app_error'),
|
|
{ Errors, UserTypes, PartnerCodes } = require('../helpers/constants'),
|
|
{ updateUser_put } = require('./user'), // Import user controller functions,
|
|
partnerSyncService = require('../services/partner_sync_service'),
|
|
ObjectId = require('mongodb').ObjectId,
|
|
assert = require('assert');
|
|
|
|
/**
|
|
* Helper function to find and validate a partner system user
|
|
* @param {string} customerId - Customer ID
|
|
* @param {string} partnerId - Partner ID (can be partnerCode string or ObjectId)
|
|
* @param {object} options - Additional validation options
|
|
* @param {boolean} options.requireActive - Whether to require active status (default: true)
|
|
* @param {string} options.username - Username for credential validation
|
|
* @param {string} options.password - Password for credential validation
|
|
* @returns {Promise<object>} Partner system user with populated partner and customer info
|
|
*/
|
|
async function findAndValidatePartnerSystemUser(customerId, partnerId, options = {}) {
|
|
const { requireActive = true, username, password } = options;
|
|
|
|
// Validate IDs
|
|
if (!ObjectId.isValid(customerId)) {
|
|
throw new AppParamError('Invalid customerId format');
|
|
}
|
|
|
|
let query = { parent: ObjectId(customerId) }; // Use parent field for customer lookup
|
|
|
|
// Handle partnerId - could be ObjectId or partner code string
|
|
if (ObjectId.isValid(partnerId)) {
|
|
query.partner = ObjectId(partnerId);
|
|
} else {
|
|
// If it's a string like 'SATLOC', find partner by partnerCode
|
|
const partner = await Partner.findOne({ partnerCode: partnerId }).lean();
|
|
if (!partner) {
|
|
throw new AppParamError(Errors.NOT_FOUND, `Partner not found with code: ${partnerId}`);
|
|
}
|
|
query.partner = partner._id;
|
|
}
|
|
|
|
// If username provided, include it in the lookup query
|
|
if (username) query.username = username;
|
|
|
|
// Find the partner system user
|
|
const partnerSystemUser = await PartnerSystemUser.findOne(query)
|
|
.populate('partner', 'name partnerCode')
|
|
.populate('parent', 'name username'); // parent = customer relationship
|
|
|
|
if (!partnerSystemUser) {
|
|
throw new AppParamError(Errors.NOT_FOUND, `Partner system user not found for customerId: ${customerId} and partnerId: ${partnerId}`);
|
|
}
|
|
|
|
if (requireActive && !partnerSystemUser.active) {
|
|
throw new AppParamError(Errors.INACTIVE, 'Partner system user is inactive');
|
|
}
|
|
|
|
// Validate password if provided (username was already matched via query)
|
|
if (password && String(partnerSystemUser.password).localeCompare(String(password)) !== 0) {
|
|
throw new AppAuthError(Errors.WRONG_CREDENTIAL);
|
|
}
|
|
|
|
return partnerSystemUser;
|
|
}
|
|
|
|
// Create a new partner organization
|
|
async function createPartner_post(req, res) {
|
|
const _partner = req.body;
|
|
|
|
assert(_partner && _partner.name, AppParamError.create());
|
|
|
|
delete _partner._id;
|
|
const partner = new Partner(Partner.toUser(_partner));
|
|
const newPartner = await partner.save();
|
|
|
|
res.json(newPartner);
|
|
}
|
|
|
|
// Create a new partner system user (RESTful POST /systemUsers)
|
|
async function createSystemUser_post(req, res) {
|
|
const input = req.body;
|
|
assert(input && input.partnerId && input.customerId && input.username && input.password, AppParamError.create());
|
|
|
|
// Validate both partnerId and customerId exist in one query
|
|
const parentUsers = await User.find({ _id: { $in: [ObjectId(input.partnerId), ObjectId(input.customerId)] } });
|
|
if (parentUsers.length < 2) AppParamError.throw();
|
|
|
|
const partnerSystemUser = new PartnerSystemUser({
|
|
kind: UserTypes.PARTNER_SYSTEM_USER,
|
|
name: input.name || `${input.username} (Partner System)`,
|
|
username: input.username,
|
|
password: input.password,
|
|
active: input.active !== undefined ? input.active : true,
|
|
// Parent relationship - links to the applicator (customer) user
|
|
parent: input.customerId, // This is the customer/applicator reference
|
|
// Partner system user fields
|
|
partner: input.partnerId,
|
|
// NOTE: 'customer' field removed - use 'parent' for customer relationship
|
|
// partnerUserId: input.partnerUserId,
|
|
// partnerUsername: input.partnerUsername,
|
|
companyId: input?.companyId,
|
|
apiKey: input?.apiKey,
|
|
apiSecret: input?.apiSecret,
|
|
metadata: input?.metadata
|
|
});
|
|
|
|
const newPartnerSystemUser = await partnerSystemUser.save();
|
|
res.json(newPartnerSystemUser);
|
|
}
|
|
|
|
// Get all partners
|
|
async function getPartners_post(req, res) {
|
|
const partners = await Partner.find().lean();
|
|
res.json(partners);
|
|
}
|
|
|
|
// Get partner by ID
|
|
async function getPartnerById_post(req, res) {
|
|
const { partner } = req.body;
|
|
|
|
assert(partner, AppParamError.create());
|
|
|
|
const partnerData = await Partner.findById(partner).lean();
|
|
if (!partnerData) AppParamError.throw(`Partner not found with ID: ${partner}`);
|
|
if (!partnerData) AppParamError.throw(Errors.NOT_FOUND, `Partner not found with ID: ${partner}`);
|
|
|
|
res.json(partnerData);
|
|
}
|
|
|
|
// Update a partner (RESTful PUT /partners/:id) - reuses user controller logic
|
|
async function updatePartner_put(req, res) {
|
|
if (!req.body.kind) {
|
|
req.body.kind = UserTypes.PARTNER;
|
|
}
|
|
// Reuse the user controller's updateUser_put function directly
|
|
return updateUser_put(req, res);
|
|
}
|
|
|
|
// Update a partner system user (only updates fields present in request body)
|
|
async function updateSystemUser_post(req, res) {
|
|
const systemUserId = req.params.id;
|
|
const input = req.body;
|
|
|
|
assert(systemUserId, AppParamError.create());
|
|
|
|
// Build update object with only fields present in input
|
|
const updateFields = {};
|
|
const allowedFields = [
|
|
'name', 'username', 'active', 'partner', 'parent', // parent = customer relationship
|
|
'partnerUserId', 'partnerUsername', 'companyId',
|
|
'apiKey', 'apiSecret', 'metadata'
|
|
];
|
|
|
|
// Map 'customer' to 'parent' for backward compatibility
|
|
if (input.hasOwnProperty('customer') && !input.hasOwnProperty('parent')) {
|
|
input.parent = input.customer;
|
|
}
|
|
|
|
allowedFields.forEach(field => {
|
|
if (input.hasOwnProperty(field)) {
|
|
updateFields[field] = input[field];
|
|
}
|
|
});
|
|
|
|
let partnerSystemUser;
|
|
|
|
// If no fields to update, just return the current record
|
|
if (Object.keys(updateFields).length === 0) {
|
|
partnerSystemUser = await PartnerSystemUser.findById(systemUserId).lean();
|
|
} else {
|
|
partnerSystemUser = await PartnerSystemUser.findByIdAndUpdate(
|
|
systemUserId,
|
|
{ $set: updateFields },
|
|
{ new: true }
|
|
).lean();
|
|
}
|
|
|
|
if (!partnerSystemUser) AppParamError.throw(Errors.NOT_FOUND);
|
|
|
|
res.json(partnerSystemUser);
|
|
}
|
|
|
|
// Delete a partner (soft delete - mark as inactive)
|
|
async function deletePartner(req, res) {
|
|
const { id } = req.params;
|
|
|
|
assert(id, AppParamError.create());
|
|
|
|
const partner = await Partner.findByIdAndUpdate(
|
|
id,
|
|
{ active: false },
|
|
{ new: true }
|
|
).lean();
|
|
|
|
if (!partner) AppParamError.throw(Errors.NOT_FOUND);
|
|
|
|
res.json({ ok: true });
|
|
}
|
|
|
|
// Sync data from partner system
|
|
async function syncData_post(req, res) {
|
|
try {
|
|
const { partnerCode, customer } = req.body;
|
|
|
|
assert(partnerCode && customer, AppParamError.create());
|
|
|
|
// Implementation depends on specific partner integration
|
|
// This is a placeholder for partner sync functionality
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Partner data sync initiated',
|
|
partnerCode,
|
|
customer
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Upload job to partner system
|
|
async function uploadJob_post(req, res) {
|
|
try {
|
|
const { partnerCode, customer, jobData } = req.body;
|
|
|
|
assert(partnerCode && customer && jobData, AppParamError.create());
|
|
|
|
// Implementation depends on specific partner integration
|
|
// This is a placeholder for partner job upload functionality
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Job upload to partner system initiated',
|
|
partnerCode,
|
|
customer,
|
|
jobId: jobData.jobId
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
async function getPartners_get(req, res) {
|
|
const partners = await Partner.find({ markedDelete: { $ne: true } }).lean();
|
|
res.json(partners);
|
|
}
|
|
|
|
async function getPartnerById_get(req, res) {
|
|
const { id } = req.params;
|
|
|
|
assert(id, AppParamError.create());
|
|
|
|
const partner = await Partner.findById(id).lean();
|
|
if (!partner) AppParamError.throw(Errors.NOT_FOUND, `Partner not found with ID: ${id}`);
|
|
|
|
res.json(partner);
|
|
}
|
|
|
|
async function getSystemUsers_get(req, res) {
|
|
const { partnerId, customerId } = req.query;
|
|
|
|
// Validate mandatory parameters
|
|
assert(ObjectId.isValid(partnerId) && ObjectId.isValid(customerId), AppParamError.create());
|
|
|
|
const query = {
|
|
partner: partnerId,
|
|
parent: customerId // Use parent field for customer lookup
|
|
};
|
|
|
|
const partnerSystemUsers = await PartnerSystemUser.find(query)
|
|
.populate({ path: 'partner', select: 'name partnerCode' })
|
|
.populate({ path: 'parent', select: 'name username' }) // parent = customer
|
|
.lean();
|
|
|
|
res.json(partnerSystemUsers);
|
|
}
|
|
|
|
// Get current (first active) system user for a partner+customer pair
|
|
async function getCurrentSystemUser_get(req, res) {
|
|
const { partnerId, customerId } = req.query;
|
|
|
|
assert(ObjectId.isValid(partnerId) && ObjectId.isValid(customerId), AppParamError.create());
|
|
|
|
const partnerSystemUser = await PartnerSystemUser.findOne({
|
|
partner: ObjectId(partnerId),
|
|
parent: ObjectId(customerId),
|
|
active: true,
|
|
markedDelete: { $ne: true }
|
|
})
|
|
.populate({ path: 'partner', select: 'name partnerCode' })
|
|
.populate({ path: 'parent', select: 'name username' })
|
|
.lean();
|
|
|
|
if (!partnerSystemUser) AppParamError.throw(Errors.NOT_FOUND);
|
|
|
|
res.json(partnerSystemUser);
|
|
}
|
|
|
|
// Get system user by ID (RESTful GET /systemUsers/:id)
|
|
async function getSystemUser_get(req, res) {
|
|
const { id } = req.params;
|
|
|
|
assert(id, AppParamError.create());
|
|
|
|
const partnerSystemUser = await PartnerSystemUser.findById(id)
|
|
.populate('partner', 'name partnerCode')
|
|
.populate('parent', 'name username') // parent = customer
|
|
.lean();
|
|
|
|
if (!partnerSystemUser) AppParamError.throw(Errors.NOT_FOUND);
|
|
|
|
res.json(partnerSystemUser);
|
|
}
|
|
|
|
// Update system user (RESTful PUT /systemUsers/:id) - reuses user controller logic
|
|
async function updateSystemUser_put(req, res) {
|
|
if (!req.body.kind) {
|
|
req.body.kind = UserTypes.PARTNER_SYSTEM_USER;
|
|
}
|
|
// Reuse the user controller's updateUser_put function directly
|
|
return updateUser_put(req, res);
|
|
}
|
|
|
|
// Delete system user (soft delete - mark as inactive)
|
|
async function deleteSystemUser(req, res) {
|
|
const { id } = req.params;
|
|
|
|
assert(id, AppParamError.create());
|
|
|
|
const partnerSystemUser = await PartnerSystemUser.findByIdAndUpdate(
|
|
id,
|
|
{ active: false },
|
|
{ new: true }
|
|
).lean();
|
|
|
|
if (!partnerSystemUser) AppParamError.throw(Errors.NOT_FOUND);
|
|
|
|
res.json({ ok: true });
|
|
}
|
|
|
|
// Get all customers for a given partner with subscription information
|
|
async function getPartnerCustomers_get(req, res) {
|
|
const { partnerId } = req.query;
|
|
|
|
if (!partnerId) AppParamError.throw();
|
|
|
|
// Aggregate customers linked to the partner through partner system users
|
|
const customers = await PartnerSystemUser.aggregate([
|
|
// Match partner system users for the given partner
|
|
{
|
|
$match: { partner: ObjectId(partnerId), active: true, markedDelete: { $ne: true } }
|
|
},
|
|
// Lookup the customer information (using parent field)
|
|
{
|
|
$lookup: {
|
|
from: 'users',
|
|
localField: 'parent', // parent = customer relationship
|
|
foreignField: '_id',
|
|
as: 'customerInfo'
|
|
}
|
|
},
|
|
// Unwind customer info
|
|
{
|
|
$unwind: '$customerInfo'
|
|
},
|
|
// Filter only active customers
|
|
{
|
|
$match: { 'customerInfo.active': true, 'customerInfo.markedDelete': { $ne: true } }
|
|
},
|
|
// Project the fields we want to return
|
|
{
|
|
$project: {
|
|
_id: '$customerInfo._id',
|
|
name: '$customerInfo.name',
|
|
email: '$customerInfo.email',
|
|
username: '$customerInfo.username',
|
|
contact: '$customerInfo.contact',
|
|
active: '$customerInfo.active',
|
|
country: '$customerInfo.country',
|
|
createdAt: '$customerInfo.createdAt',
|
|
updatedAt: '$customerInfo.updatedAt',
|
|
// Temporarily include membership for processing
|
|
'membership.subscriptions': '$customerInfo.membership.subscriptions'
|
|
}
|
|
},
|
|
// Add computed fields for package information
|
|
{
|
|
$addFields: {
|
|
packageInfo: {
|
|
$map: {
|
|
input: {
|
|
$filter: {
|
|
input: { $ifNull: ['$membership.subscriptions', []] },
|
|
cond: { $eq: ['$$this.type', 'package'] }
|
|
}
|
|
},
|
|
as: 'sub',
|
|
in: {
|
|
packageName: {
|
|
$arrayElemAt: ['$$sub.items.price', 0] // Get the first (and usually only) price/lookup_key
|
|
},
|
|
status: '$$sub.status',
|
|
startDate: {
|
|
$dateFromParts: {
|
|
year: { $year: { $toDate: { $multiply: ['$$sub.periodStart', 1000] } } },
|
|
month: { $month: { $toDate: { $multiply: ['$$sub.periodStart', 1000] } } },
|
|
day: { $dayOfMonth: { $toDate: { $multiply: ['$$sub.periodStart', 1000] } } }
|
|
}
|
|
},
|
|
endDate: {
|
|
$dateFromParts: {
|
|
year: { $year: { $toDate: { $multiply: ['$$sub.periodEnd', 1000] } } },
|
|
month: { $month: { $toDate: { $multiply: ['$$sub.periodEnd', 1000] } } },
|
|
day: { $dayOfMonth: { $toDate: { $multiply: ['$$sub.periodEnd', 1000] } } }
|
|
}
|
|
},
|
|
recurring: '$$sub.recurring'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
// Remove the membership field from final output
|
|
{
|
|
$project: {
|
|
membership: 0
|
|
}
|
|
},
|
|
// Sort by customer name
|
|
{
|
|
$sort: { name: 1 }
|
|
}
|
|
]);
|
|
|
|
res.json(customers);
|
|
}
|
|
|
|
// Test authentication/login of a partner system user
|
|
async function testPartnerAuth_post(req, res) {
|
|
const { customerId, partnerId, username, password } = req.body;
|
|
|
|
if (!customerId || !partnerId || !username || !password) {
|
|
AppParamError.throw(Errors.INVALID_PARAM);
|
|
}
|
|
// Use helper function to find and validate partner system user
|
|
const partnerSystemUser = await findAndValidatePartnerSystemUser(customerId, partnerId, {
|
|
requireActive: true,
|
|
username,
|
|
password
|
|
});
|
|
|
|
// Try to get partner service and test authentication
|
|
const partnerCode = partnerSystemUser.partner.partnerCode;
|
|
const partnerServiceFactory = require('../services/partner_service_factory');
|
|
|
|
// Check if partner service is supported
|
|
if (!partnerServiceFactory.hasService(partnerCode)) {
|
|
AppParamError.throw(Errors.NOT_SUPPORTED, `Partner service not supported for code: ${partnerCode}`);
|
|
}
|
|
|
|
// Get the partner service dynamically
|
|
const partnerService = partnerServiceFactory.getService(partnerCode);
|
|
const partnerConfig = require('../helpers/partner_config');
|
|
|
|
// Get credentials for authentication
|
|
const credentials = partnerConfig.getApiCredentials(partnerSystemUser, partnerCode);
|
|
|
|
// Use authenticate() without caching for testing purposes
|
|
const authResult = await partnerService.authenticate({ username, password }, customerId);
|
|
|
|
res.json({
|
|
ok: true,
|
|
...!env.PRODUCTION ? {} : { authResult } // Include authResult only in non-production for debugging
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get aircraft list from partner system
|
|
* GET /api/partners/aircraft?partnerId=SATLOC&customerId=<customerId>
|
|
* OR GET /api/partners/aircraft?partnerId=<ObjectId>&customerId=<customerId>
|
|
*/
|
|
async function getPartnerAircraft_get(req, res) {
|
|
const { partnerId, customerId } = req.query;
|
|
|
|
// Validate required parameters
|
|
if (!partnerId || !ObjectId.isValid(customerId)) AppParamError.throw();
|
|
|
|
try {
|
|
let partnerCode;
|
|
|
|
// Handle partnerId - could be ObjectId or partner code string
|
|
if (ObjectId.isValid(partnerId)) {
|
|
// If it's an ObjectId, find partner by ID to get partnerCode
|
|
const partner = await Partner.findById(partnerId).lean();
|
|
if (!partner) {
|
|
return res.status(422).json({
|
|
success: false, partnerId, customerId, error: `Partner not found with ID: ${partnerId}`
|
|
});
|
|
}
|
|
partnerCode = partner.partnerCode;
|
|
} else {
|
|
// If it's a string like 'SATLOC', use it directly as partnerCode
|
|
partnerCode = partnerId;
|
|
}
|
|
|
|
// Get aircraft list from partner service using partnerCode
|
|
const result = await partnerSyncService.getPartnerAircraftList(partnerCode, customerId);
|
|
|
|
if (result.success) {
|
|
res.json({
|
|
success: true,
|
|
partnerId,
|
|
customerId,
|
|
partnerCode,
|
|
aircraft: result.aircraft || []
|
|
});
|
|
} else {
|
|
// Don't hardcode 400 - let service errors bubble up or return appropriate status
|
|
res.status(422).json({
|
|
success: false,
|
|
partnerId,
|
|
customerId,
|
|
partnerCode,
|
|
error: result.error
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
// Let AppParamError and other app errors bubble up to error handler middleware
|
|
if (error instanceof AppParamError || error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
// Handle unexpected errors
|
|
res.status(500).json({
|
|
success: false,
|
|
partnerId,
|
|
customerId,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
createPartner_post,
|
|
createSystemUser_post,
|
|
getPartners_post,
|
|
getPartners_get,
|
|
getSystemUsers_get,
|
|
getPartnerById_post,
|
|
getPartnerById_get,
|
|
updatePartner_put,
|
|
updateSystemUser_post,
|
|
deletePartner,
|
|
syncData_post,
|
|
uploadJob_post,
|
|
getCurrentSystemUser_get,
|
|
getSystemUser_get,
|
|
updateSystemUser_put,
|
|
deleteSystemUser,
|
|
getPartnerCustomers_get,
|
|
testPartnerAuth_post,
|
|
getPartnerAircraft_get
|
|
}; |