purchase-mcp-server
Version:
338 lines (337 loc) • 14.7 kB
JavaScript
/**
* Company Name Filtering Utilities
* Helper functions for filtering data based on company IMO numbers
*
* This implementation includes:
* - In-memory caching with TTL (5 minutes)
* - MongoDB connection pooling
* - Set-based IMO lookups (O(1) instead of O(n))
* - Cached environment variables
*/
import { MongoClient } from 'mongodb';
/**
* Check if IMO filtering should be bypassed for admin companies
*/
function shouldBypassImoFiltering(companyName) {
const adminCompanies = ['admin', 'administrator', 'superadmin', 'syia'];
return adminCompanies.some(admin => companyName.toLowerCase().includes(admin.toLowerCase()));
}
/**
* Optimized Company Filter Manager with caching and connection pooling
*/
class CompanyFilterManager {
// Cache structure: Map<"companyName:dbName", { fleetImos: Set<string>, vesselImos: Set<string>, allImos: Set<string>, timestamp: number }>
imoCache = new Map();
// Connection pool: Map<"mongoUri", MongoClient>
mongoClients = new Map();
// Cache TTL: 5 minutes
CACHE_TTL = 5 * 60 * 1000;
// Cached config values
cachedCompanyName = null;
cachedDbConfig = null;
/**
* Get company name (cached)
*/
getCompanyName() {
if (this.cachedCompanyName === null) {
this.cachedCompanyName = process.env.COMPANY_NAME || '';
}
return this.cachedCompanyName || '';
}
/**
* Get database configuration (cached)
*/
getDbConfig() {
if (this.cachedDbConfig === null) {
this.cachedDbConfig = {
dbName: process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || process.env.COMPANY_DB_NAME || '',
mongoUri: process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || process.env.COMPANY_DB_URI || ''
};
}
return this.cachedDbConfig;
}
/**
* Get or create MongoDB client (connection pooling)
*/
async getMongoClient(mongoUri) {
if (!this.mongoClients.has(mongoUri)) {
const client = new MongoClient(mongoUri, {
maxPoolSize: 10,
minPoolSize: 2,
maxIdleTimeMS: 30000
});
await client.connect();
this.mongoClients.set(mongoUri, client);
}
return this.mongoClients.get(mongoUri);
}
/**
* Get company IMO sets with caching (returns separate fleet and vessel IMO sets)
* Extracts both 'imo' (fleet IMOs) and 'imoList' (vessel IMOs) from all matching documents
*/
async getCompanyImoSets(companyName, dbName, mongoUri, collectionName = 'common_group_details') {
const cacheKey = `${companyName}:${dbName}`;
const cached = this.imoCache.get(cacheKey);
// Return from cache if valid
if (cached && (Date.now() - cached.timestamp) < this.CACHE_TTL) {
return {
fleetImos: cached.fleetImos,
vesselImos: cached.vesselImos,
allImos: cached.allImos
};
}
// Fetch from MongoDB
if (!dbName || !mongoUri || !companyName) {
const emptySet = new Set();
return { fleetImos: emptySet, vesselImos: emptySet, allImos: emptySet };
}
try {
const client = await this.getMongoClient(mongoUri);
const db = client.db(dbName);
const collection = db.collection(collectionName);
// Query for ALL documents matching companyName or groupName
const results = await collection.find({ $or: [{ companyName: companyName }, { groupName: companyName }] }, { projection: { imo: 1, imoList: 1, _id: 0 } }).toArray();
// Separate fleet IMOs (imo field) and vessel IMOs (imoList arrays)
const fleetImos = new Set();
const vesselImos = new Set();
// Extract fleet IMOs (imo field) and vessel IMOs (imoList array) from each document
for (const doc of results) {
// Add fleet IMO (imo field) if present - these are valid fleet IMOs
if (doc.imo) {
fleetImos.add(String(doc.imo));
}
// Add vessel IMOs (imoList array) if present - these are valid vessel IMOs
if (doc.imoList && Array.isArray(doc.imoList)) {
for (const imo of doc.imoList) {
vesselImos.add(String(imo));
}
}
}
// Combine both for backward compatibility
const allImos = new Set([...fleetImos, ...vesselImos]);
// Update cache
this.imoCache.set(cacheKey, {
fleetImos,
vesselImos,
allImos,
timestamp: Date.now()
});
return { fleetImos, vesselImos, allImos };
}
catch (error) {
// Removed console.error to avoid interfering with MCP JSON-RPC protocol
const emptySet = new Set();
return { fleetImos: emptySet, vesselImos: emptySet, allImos: emptySet };
}
}
/**
* Get company IMO set with caching (returns combined Set for backward compatibility)
* Extracts both 'imo' (fleet IMOs) and 'imoList' (vessel IMOs) from all matching documents
*/
async getCompanyImoSet(companyName, dbName, mongoUri, collectionName = 'common_group_details') {
const { allImos } = await this.getCompanyImoSets(companyName, dbName, mongoUri, collectionName);
return allImos;
}
/**
* Validate if a vessel IMO belongs to the company (validates against vessel IMO list from imoList arrays)
*/
async isValidVesselImoForCompany(imo, companyName, dbName, mongoUri) {
const finalCompanyName = companyName || this.getCompanyName();
if (!finalCompanyName) {
return true; // If no company name, allow all
}
// Bypass validation for "Synergy" company
if (finalCompanyName === "Synergy") {
return true;
}
// Bypass validation for admin companies
if (shouldBypassImoFiltering(finalCompanyName)) {
return true;
}
// Use provided dbName/mongoUri or fallback to cached config values
const configValues = this.getDbConfig();
const finalDbName = dbName || configValues.dbName;
const finalMongoUri = mongoUri || configValues.mongoUri;
if (!finalDbName || !finalMongoUri) {
// Removed console.warn to avoid interfering with MCP JSON-RPC protocol
return true; // Allow if config is missing
}
try {
const { vesselImos } = await this.getCompanyImoSets(finalCompanyName, finalDbName, finalMongoUri);
return vesselImos.has(String(imo));
}
catch (error) {
// Removed console.error to avoid interfering with MCP JSON-RPC protocol
return true; // Allow on error to prevent blocking
}
}
/**
* Validate if a fleet IMO belongs to the company (validates against fleet IMO list from imo field)
*/
async isValidFleetImoForCompany(fleetImo, companyName, dbName, mongoUri) {
const finalCompanyName = companyName || this.getCompanyName();
if (!finalCompanyName) {
return true; // If no company name, allow all
}
// Bypass validation for "Synergy" company
if (finalCompanyName === "Synergy") {
return true;
}
// Bypass validation for admin companies
if (shouldBypassImoFiltering(finalCompanyName)) {
return true;
}
// Use provided dbName/mongoUri or fallback to cached config values
const configValues = this.getDbConfig();
const finalDbName = dbName || configValues.dbName;
const finalMongoUri = mongoUri || configValues.mongoUri;
if (!finalDbName || !finalMongoUri) {
// Removed console.warn to avoid interfering with MCP JSON-RPC protocol
return true; // Allow if config is missing
}
try {
const { fleetImos } = await this.getCompanyImoSets(finalCompanyName, finalDbName, finalMongoUri);
return fleetImos.has(String(fleetImo));
}
catch (error) {
// Removed console.error to avoid interfering with MCP JSON-RPC protocol
return true; // Allow on error to prevent blocking
}
}
/**
* Validate if an IMO belongs to the company (backward compatibility - validates against vessel IMOs)
* @deprecated Use isValidVesselImoForCompany or isValidFleetImoForCompany instead
*/
async isValidImoForCompany(imo, companyName, dbName, mongoUri) {
// Default to vessel IMO validation for backward compatibility
return this.isValidVesselImoForCompany(imo, companyName, dbName, mongoUri);
}
/**
* Update Typesense filter with company IMO numbers (optimized)
*/
async updateTypesenseFilterWithCompanyImos(filter, dbName, mongoUri) {
const companyName = this.getCompanyName();
if (!companyName) {
return filter;
}
// Bypass filtering for "Synergy" company
if (companyName === "Synergy") {
return filter;
}
// Bypass filtering for admin companies
if (shouldBypassImoFiltering(companyName)) {
return filter;
}
// Use provided dbName/mongoUri or fallback to cached config values
const configValues = this.getDbConfig();
const finalDbName = dbName || configValues.dbName;
const finalMongoUri = mongoUri || configValues.mongoUri;
if (!finalDbName || !finalMongoUri) {
// Removed console.warn to avoid interfering with MCP JSON-RPC protocol
return filter;
}
try {
const imoSet = await this.getCompanyImoSet(companyName, finalDbName, finalMongoUri);
if (imoSet.size === 0) {
// Removed console.warn to avoid interfering with MCP JSON-RPC protocol
// If no company IMO numbers found, return a filter that matches nothing for security
// This prevents access to all data when company configuration is missing
return filter ? `${filter} && imo:0` : 'imo:0';
}
const imoFilter = `imo:[${Array.from(imoSet).join(",")}]`;
if (filter && filter.trim()) {
return `${filter} && ${imoFilter}`;
}
else {
return imoFilter;
}
}
catch (error) {
// Removed console.error to avoid interfering with MCP JSON-RPC protocol
return filter; // Return original filter on error
}
}
/**
* Get company IMO numbers as array (for backward compatibility)
*/
async getCompanyImoNumbers(companyName, dbName, mongoUri, collectionName = 'common_group_details') {
const imoSet = await this.getCompanyImoSet(companyName, dbName, mongoUri, collectionName);
return Array.from(imoSet);
}
/**
* Clear cache (useful for testing or forced refresh)
*/
clearCache() {
this.imoCache.clear();
this.cachedCompanyName = null;
this.cachedDbConfig = null;
}
/**
* Close all MongoDB connections (cleanup)
*/
async closeConnections() {
const closePromises = Array.from(this.mongoClients.values()).map(client => client.close());
await Promise.all(closePromises);
this.mongoClients.clear();
}
}
// Singleton instance
export const companyFilter = new CompanyFilterManager();
// Export functions for backward compatibility
export async function fetchCompanyImoNumbers(companyName, dbName, mongoUri, collectionName = 'common_group_details') {
return companyFilter.getCompanyImoNumbers(companyName, dbName, mongoUri, collectionName);
}
export async function isValidImoForCompany(imo, companyName, dbName, mongoUri) {
return companyFilter.isValidImoForCompany(imo, companyName, dbName, mongoUri);
}
export async function isValidVesselImoForCompany(imo, companyName, dbName, mongoUri) {
return companyFilter.isValidVesselImoForCompany(imo, companyName, dbName, mongoUri);
}
export async function isValidFleetImoForCompany(fleetImo, companyName, dbName, mongoUri) {
return companyFilter.isValidFleetImoForCompany(fleetImo, companyName, dbName, mongoUri);
}
export async function updateTypesenseFilterWithCompanyImos(filter, dbName, mongoUri) {
return companyFilter.updateTypesenseFilterWithCompanyImos(filter, dbName, mongoUri);
}
/**
* Filter IMO list to only include IMOs that belong to the company
*/
export async function filterImoListByCompany(imoList, dbName, mongoUri) {
const companyName = process.env.COMPANY_NAME || '';
if (!companyName) {
return imoList.map(imo => typeof imo === 'number' ? imo : parseInt(String(imo), 10));
}
// Bypass filtering for "Synergy" company
if (companyName === "Synergy") {
return imoList.map(imo => typeof imo === 'number' ? imo : parseInt(String(imo), 10));
}
// Bypass filtering for admin companies
if (shouldBypassImoFiltering(companyName)) {
return imoList.map(imo => typeof imo === 'number' ? imo : parseInt(String(imo), 10));
}
// Get company IMO set
const configValues = {
dbName: dbName || process.env.GROUP_DETAILS_DB_NAME || process.env.FLEET_DISTRIBUTION_DB_NAME || process.env.COMPANY_DB_NAME || '',
mongoUri: mongoUri || process.env.GROUP_DETAILS_MONGO_URI || process.env.FLEET_DISTRIBUTION_MONGO_URI || process.env.COMPANY_DB_URI || ''
};
if (!configValues.dbName || !configValues.mongoUri) {
// Removed console.warn to avoid interfering with MCP JSON-RPC protocol
return imoList.map(imo => typeof imo === 'number' ? imo : parseInt(String(imo), 10));
}
try {
const imoSet = await companyFilter.getCompanyImoSet(companyName, configValues.dbName, configValues.mongoUri);
// Filter IMO list to only include company IMOs
const filteredList = imoList
.map(imo => String(imo))
.filter(imo => imoSet.has(imo))
.map(imo => parseInt(imo, 10));
return filteredList;
}
catch (error) {
// Removed console.error to avoid interfering with MCP JSON-RPC protocol
// Return empty list on error for security
return [];
}
}
// Export shouldBypassImoFiltering for use in handlers
export { shouldBypassImoFiltering };