purchase-mcp-server
Version:
Purchase and budget management server handling requisitions, purchase orders, expenses, budgets, and vendor management with ERP access for data extraction
231 lines • 8.19 kB
JavaScript
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { MongoClient } from 'mongodb';
import { logger } from '../index.js';
// ============================================================================
// 🔧 DOMAIN-SPECIFIC CONFIGURATION - CUSTOMIZE FOR NEW MCP SERVERS
// ============================================================================
/**
* Companies that should skip validation entirely
* CUSTOMIZE: Add your development/test company names here
*/
const SKIP_VALIDATION_COMPANIES = ['synergy', 'development', 'test'];
/**
* Database collection and field names for your authorization system
* CUSTOMIZE: Change these to match your database schema
*/
const IMO_COLLECTION_NAME = 'common_group_details';
const IMO_FIELD_NAME = 'imoList';
const COMPANY_FIELD_NAME = 'groupName';
const DATABASE_NAME = 'syia-etl-dev';
/**
* Cache file configuration
* CUSTOMIZE: Change cache file name if needed
*/
const CACHE_FILENAME = 'company-imos.json';
// ============================================================================
// END CUSTOMIZABLE SECTION
// ============================================================================
// File path setup
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const DATA_DIR = join(__dirname, '../../data');
const CACHE_FILE = join(DATA_DIR, CACHE_FILENAME);
// In-memory storage
let companyImoCache = null;
// MongoDB connection options for better performance
const MONGO_OPTIONS = {
maxPoolSize: 1,
minPoolSize: 0,
maxIdleTimeMS: 30000,
serverSelectionTimeoutMS: 10000,
connectTimeoutMS: 10000,
};
/**
* Ensure data directory exists
*/
function ensureDataDirectory() {
if (!existsSync(DATA_DIR)) {
mkdirSync(DATA_DIR, { recursive: true });
logger.debug('Created data directory for IMO cache');
}
}
/**
* Fetch IMO numbers for a company from MongoDB
*/
async function fetchCompanyImoNumbers(companyName) {
let client = null;
try {
logger.info(`Fetching IMO numbers for company: ${companyName}`);
if (!process.env.MONGODB_ETL_DEV_DATA_URI) {
throw new Error('MONGODB_ETL_DEV_DATA_URI environment variable is required');
}
// Create optimized MongoDB client
client = new MongoClient(process.env.MONGODB_ETL_DEV_DATA_URI, MONGO_OPTIONS);
await client.connect();
const db = client.db(DATABASE_NAME);
const collection = db.collection(IMO_COLLECTION_NAME);
// Query with timeout for reliability
const result = await collection.findOne({ [COMPANY_FIELD_NAME]: companyName }, {
projection: { imoList: 1, _id: 0 },
maxTimeMS: 10000 // 10 second timeout
});
if (!result?.[IMO_FIELD_NAME] || !Array.isArray(result[IMO_FIELD_NAME])) {
logger.warn(`No IMO numbers found for company: ${companyName}`);
return [];
}
// Convert and validate IMO numbers
const imoNumbers = result[IMO_FIELD_NAME]
.filter(imo => typeof imo === 'number' && imo > 0)
.map(imo => imo.toString());
logger.info(`Found ${imoNumbers.length} valid IMO numbers for company: ${companyName}`);
// Update in-memory cache
companyImoCache = {
companyName,
imoNumbers,
lastUpdated: new Date().toISOString()
};
return imoNumbers;
}
catch (error) {
logger.error(`Error fetching IMO numbers for company ${companyName}:`, error);
throw error;
}
finally {
// Always close connection
if (client) {
try {
await client.close();
}
catch (closeError) {
logger.warn('Error closing MongoDB connection:', closeError);
}
}
}
}
/**
* Get cached company IMO numbers from memory
*/
function getCompanyImoNumbers() {
return companyImoCache?.imoNumbers || [];
}
/**
* Load cached IMO numbers from file
*/
export function loadCachedImos() {
try {
if (!existsSync(CACHE_FILE)) {
logger.debug('IMO cache file does not exist');
return [];
}
const data = readFileSync(CACHE_FILE, 'utf8');
const cacheData = JSON.parse(data);
// Validate cache structure
if (!cacheData?.imoNumbers || !Array.isArray(cacheData.imoNumbers)) {
logger.warn('Invalid cache file structure, ignoring cached data');
return [];
}
// Update in-memory cache if valid
companyImoCache = cacheData;
logger.debug(`Loaded ${cacheData.imoNumbers.length} IMO numbers from cache file`);
return cacheData.imoNumbers;
}
catch (error) {
logger.warn('Failed to load cached IMOs, cache file may be corrupted:', error);
return [];
}
}
/**
* Save IMO numbers to cache file
*/
export function saveCachedImos(imos, companyName) {
try {
ensureDataDirectory();
const cacheData = {
companyName,
imoNumbers: imos,
lastUpdated: new Date().toISOString()
};
writeFileSync(CACHE_FILE, JSON.stringify(cacheData, null, 2), 'utf8');
logger.info(`Successfully cached ${imos.length} IMO numbers to file`);
}
catch (error) {
logger.error('Failed to save IMO cache to file:', error);
}
}
/**
* Check if company should skip IMO validation (e.g., for development/testing)
*/
export function shouldSkipImoValidation(companyName) {
return SKIP_VALIDATION_COMPANIES.includes(companyName.toLowerCase());
}
/**
* Initialize IMO cache for the specified company
*/
export async function initializeImoCache(companyName) {
try {
logger.info(`Initializing IMO cache for company: "${companyName}"`);
// Skip IMO initialization for special companies
if (shouldSkipImoValidation(companyName)) {
logger.info(`Skipping IMO cache initialization for company: "${companyName}" (no IMO validation required)`);
clearCache(); // Clear any existing cache
return;
}
// Clear existing cache to ensure fresh start
clearCache();
// Fetch fresh data from MongoDB
const imos = await fetchCompanyImoNumbers(companyName);
if (imos.length > 0) {
// Save to file cache for persistence
saveCachedImos(imos, companyName);
logger.info(`Successfully initialized IMO cache with ${imos.length} numbers for: "${companyName}"`);
}
else {
logger.warn(`No IMO numbers found for company: "${companyName}". Cache remains empty.`);
}
}
catch (error) {
logger.error(`Failed to initialize IMO cache for company "${companyName}":`, error);
// Try to load from file cache as fallback
const cachedImos = loadCachedImos();
if (cachedImos.length > 0) {
logger.info(`Using cached IMO data as fallback (${cachedImos.length} IMOs)`);
}
}
}
/**
* Get company IMOs with intelligent fallback strategy
*/
export function getCompanyImosWithFallback() {
// Try in-memory cache first (fastest)
let imos = getCompanyImoNumbers();
// Fallback to file cache if memory is empty
if (imos.length === 0) {
imos = loadCachedImos();
if (imos.length > 0) {
logger.info(`Using cached IMO numbers from file (${imos.length} IMOs)`);
}
}
return imos;
}
/**
* Get cache status for monitoring/debugging
*/
export function getCacheStatus() {
return {
inMemory: companyImoCache !== null,
inMemoryCount: companyImoCache?.imoNumbers?.length || 0,
fileExists: existsSync(CACHE_FILE),
lastUpdated: companyImoCache?.lastUpdated,
companyName: companyImoCache?.companyName
};
}
/**
* Clear all cached data (for testing/reset purposes)
*/
export function clearCache() {
companyImoCache = null;
logger.debug('Cleared in-memory IMO cache');
}
//# sourceMappingURL=imoCache.js.map