185 lines
6.1 KiB
JavaScript
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,
|
|
}; |