Files
mcp-vultr/not_used/customerDb.js.backup

171 lines
5.9 KiB
Plaintext

const mysql = require('mysql2/promise');
// Create a connection pool
const pool = mysql.createPool({
host: process.env.BILLING_DB_HOST,
user: process.env.BILLING_DB_USER,
password: process.env.BILLING_DB_PASS,
database: process.env.BILLING_DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
async function getCustomerByPhone(phoneNumber) {
try {
// Normalize phone number for search
// User sends e.g. "62812345678" or "0812345678"
// Database has "no_wa" column which seems to vary (some have 62 prefix, some 08) based on customer.sql sample
// We will try to match broad patterns.
// If phoneNumber is 628..., we also try 08...
// If phoneNumber is 08..., we also try 628...
let searchNumbers = [phoneNumber];
let p = phoneNumber;
// Clean @c.us suffix
if (p.endsWith('@c.us')) p = p.replace('@c.us', '');
if (p.startsWith('62')) {
searchNumbers.push('0' + p.substring(2));
} else if (p.startsWith('0')) {
searchNumbers.push('62' + p.substring(1));
}
// Add the cleaned number itself just in case
if (!searchNumbers.includes(p)) searchNumbers.push(p);
// Construct query
const query = `
SELECT c.name, c.user_profile, c.c_status, c.due_date, c.address, c.no_services,
pi.name as package_name, pi.price as package_price, pi.description as package_description
FROM customer c
LEFT JOIN services s ON c.no_services = s.no_services
LEFT JOIN package_item pi ON s.item_id = pi.p_item_id
WHERE c.no_wa IN (?)
LIMIT 1
`;
const [rows] = await pool.query(query, [searchNumbers]);
if (rows.length > 0) {
return rows[0];
}
return null;
} catch (error) {
console.error("Database Error:", error);
return null; // Fail gracefully
}
}
// Test connection
async function testConnection() {
try {
const connection = await pool.getConnection();
console.log("Database connected successfully!");
connection.release();
return true;
} catch (error) {
console.error("Database connection failed:", error.message);
return false;
}
}
async function searchCustomer(query, limit = 5) {
try {
const searchTerm = `%${query}%`;
const sql = `
SELECT name, user_profile, c_status, due_date, address, no_services, no_wa
FROM customer
WHERE name LIKE ? OR no_wa LIKE ? OR c_status LIKE ? OR user_profile LIKE ?
LIMIT ?
`;
const [rows] = await pool.query(sql, [searchTerm, searchTerm, searchTerm, searchTerm, limit]);
return rows;
} catch (error) {
console.error("Database Search Error:", error);
return [];
}
}
async function getSummary() {
try {
const sql = `SELECT c_status, COUNT(*) as total FROM customer GROUP BY c_status`;
const [rows] = await pool.query(sql);
return rows;
} catch (error) {
console.error("DB Summary Error:", error);
return [];
}
}
// Safe Read-Only SQL execution for Admin AI
async function executeReadOnlyQuery(sql) {
try {
// STRICT SECURITY CHECK
// 1. Must start with SELECT
// 2. Must not contain prohibited keywords (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, REPLACE, CREATE, GRANT, REVOKE)
// 3. Simple whitespace normalization for checking
const normalizedSql = sql.trim().toUpperCase();
if (!normalizedSql.startsWith('SELECT')) {
throw new Error("Security Violation: Only SELECT queries are allowed.");
}
const prohibited = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'TRUNCATE', 'REPLACE', 'CREATE', 'GRANT', 'REVOKE', ';'];
// Note: Semicolon check prevents multiple statement injection like "SELECT *; DROP TABLE..."
for (const word of prohibited) {
// Check for whole words to avoid false positives (e.g. "UPDATE_DATE" column might trigger "UPDATE")
// A simple includes check might be too aggressive, but safer.
// Let's use regex for word boundaries or just strict includes for dangerous keywords used as verbs
// For now, strict check on prohibited keywords existing loosely
if (normalizedSql.includes(word + ' ') || normalizedSql.includes(' ' + word) || normalizedSql.endsWith(word)) { // Basic heuristic
// Better regex:
const regex = new RegExp(`\\b${word}\\b`);
if (regex.test(normalizedSql)) {
throw new Error(`Security Violation: Keyword '${word}' is prohibited.`);
}
}
}
if (sql.includes(';')) {
throw new Error("Security Violation: Multiple statements (;) are not allowed.");
}
const [rows] = await pool.query(sql);
return rows;
} catch (error) {
console.error("SQL Exec Error:", error.message);
return { error: error.message };
}
}
async function getSearchCount(query) {
try {
const searchTerm = `%${query}%`;
const sql = `
SELECT COUNT(*) as total
FROM customer
WHERE name LIKE ? OR no_wa LIKE ? OR c_status LIKE ? OR user_profile LIKE ? OR address LIKE ?
`;
// Added 'address' to search capabilities as well per user request "jumlah pelanggan di alamat tertentu"
const [rows] = await pool.query(sql, [searchTerm, searchTerm, searchTerm, searchTerm, searchTerm]);
return rows[0].total;
} catch (error) {
console.error("DB Count Error:", error);
return 0;
}
}
module.exports = {
getCustomerByPhone,
searchCustomer,
getSummary,
getSearchCount,
testConnection,
executeReadOnlyQuery
};