Files
mcp-vultr/services/customerDb.js

185 lines
6.1 KiB
JavaScript

/**
* Customer Database Module - JavaScript Wrapper for Python Implementation
*
* This module provides the same API as the original customerDb.js,
* but calls Python functions via command line interface.
*/
const { execFile } = require('child_process');
const { promisify } = require('util');
const path = require('path');
const execFileAsync = promisify(execFile);
// Determine Python path - prefer virtual environment python if available
const PYTHON_PATH = process.env.PYTHON_PATH ||
(process.platform === 'win32' ? 'python' : 'python3');
// Path to Python module
const PYTHON_MODULE_PATH = path.join(__dirname, '..', 'src', 'services', 'customer_db.py');
/**
* Call Python function with arguments
* @param {string} functionName - Name of the Python function to call
* @param {Object} args - Arguments for the function
* @returns {Promise<any>} - Result from Python function
*/
async function callPythonFunction(functionName, args = {}) {
try {
// Build command line arguments
const cliArgs = ['--function', functionName];
// Add function-specific arguments
if (functionName === 'getCustomerByPhone' && args.phone) {
cliArgs.push('--phone', args.phone);
} else if (functionName === 'searchCustomer' && args.query) {
cliArgs.push('--query', args.query);
if (args.limit) {
cliArgs.push('--limit', args.limit.toString());
}
} else if (functionName === 'getSearchCount' && args.query) {
cliArgs.push('--query', args.query);
} else if (functionName === 'executeReadOnlyQuery' && args.sql) {
cliArgs.push('--sql', args.sql);
}
// Add timeout
cliArgs.push('--timeout', '30');
// Execute Python script
const { stdout, stderr } = await execFileAsync(PYTHON_PATH, [PYTHON_MODULE_PATH, ...cliArgs], {
cwd: path.join(__dirname, '..'),
env: { ...process.env, PYTHONUNBUFFERED: '1' },
timeout: 35000, // 35 seconds timeout
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large results
});
if (stderr && stderr.trim()) {
console.warn(`Python stderr: ${stderr}`);
}
// Parse JSON output
const result = JSON.parse(stdout.trim());
// Handle Python errors
if (result && result.error) {
console.error(`Python function ${functionName} error:`, result.error);
throw new Error(result.error);
}
return result;
} catch (error) {
console.error(`Error calling Python function ${functionName}:`, error);
// Provide more helpful error messages
if (error.code === 'ENOENT') {
throw new Error(`Python not found at ${PYTHON_PATH}. Make sure Python 3.8+ is installed and in PATH.`);
} else if (error.code === 'ETIMEDOUT' || error.signal === 'SIGTERM') {
throw new Error(`Python function ${functionName} timed out after 35 seconds.`);
} else if (error instanceof SyntaxError) {
throw new Error(`Invalid JSON response from Python: ${error.message}`);
}
throw error;
}
}
/**
* Test database connection
* @returns {Promise<boolean>} - True if connection successful
*/
async function testConnection() {
try {
const result = await callPythonFunction('testConnection');
return result === true;
} catch (error) {
console.error('Database connection test failed:', error.message);
return false;
}
}
/**
* Get customer by phone number with format normalization
* @param {string} phoneNumber - Phone number to search for
* @returns {Promise<Object|null>} - Customer object or null if not found
*/
async function getCustomerByPhone(phoneNumber) {
try {
const result = await callPythonFunction('getCustomerByPhone', { phone: phoneNumber });
return result || null;
} catch (error) {
console.error('getCustomerByPhone error:', error.message);
return null;
}
}
/**
* Search customers by name, phone, status, or profile
* @param {string} query - Search term
* @param {number} limit - Maximum results (default: 5)
* @returns {Promise<Array>} - Array of customer objects
*/
async function searchCustomer(query, limit = 5) {
try {
const result = await callPythonFunction('searchCustomer', { query, limit });
return Array.isArray(result) ? result : [];
} catch (error) {
console.error('searchCustomer error:', error.message);
return [];
}
}
/**
* Get customer count grouped by status
* @returns {Promise<Array>} - Array of status count objects
*/
async function getSummary() {
try {
const result = await callPythonFunction('getSummary');
return Array.isArray(result) ? result : [];
} catch (error) {
console.error('getSummary error:', error.message);
return [];
}
}
/**
* Count customers matching search query
* @param {string} query - Search term
* @returns {Promise<number>} - Count of matching customers
*/
async function getSearchCount(query) {
try {
const result = await callPythonFunction('getSearchCount', { query });
return typeof result === 'number' ? result : 0;
} catch (error) {
console.error('getSearchCount error:', error.message);
return 0;
}
}
/**
* Execute a read-only SQL query with security checks
* @param {string} sql - SELECT SQL query to execute
* @returns {Promise<Array|Object>} - Query results or error object
*/
async function executeReadOnlyQuery(sql) {
try {
const result = await callPythonFunction('executeReadOnlyQuery', { sql });
return result;
} catch (error) {
console.error('executeReadOnlyQuery error:', error.message);
return { error: error.message };
}
}
// Export functions with same API as original
module.exports = {
getCustomerByPhone,
searchCustomer,
getSummary,
getSearchCount,
testConnection,
executeReadOnlyQuery,
};