2 * WHMCS MySQL Direct Access Service
3 * Queries WHMCS database directly for migration
6const mysql = require('mysql2/promise');
9 * WHMCS MySQL direct access service.
10 * Provides methods to query WHMCS database for migration purposes.
12class WHMCSMysqlService {
14 * Initialize WHMCS MySQL service.
15 * Creates connection pool with credentials from environment variables.
19 host: process.env.whmcs_mysql_host,
20 port: parseInt(process.env.whmcs_mysql_port || '3306'),
21 user: process.env.whmcs_mysql_user,
22 password: process.env.whmcs_mysql_password,
23 database: process.env.whmcs_mysql_db,
24 waitForConnections: true,
29 if (!this.config.host || !this.config.user || !this.config.database) {
30 throw new Error('WHMCS MySQL credentials not configured in .env');
37 * Get connection pool.
38 * Creates pool on first call and reuses for subsequent requests.
39 * @returns {Promise<mysql.Pool>} MySQL connection pool
43 this.pool = mysql.createPool(this.config);
50 * @param {string} sql - SQL query string
51 * @param {Array} [params] - Query parameters
52 * @returns {Promise<Array>} Query results
54 async query(sql, params = []) {
55 const pool = await this.getPool();
56 const [rows] = await pool.execute(sql, params);
61 * Test database connection.
62 * @returns {Promise<boolean>} True if connection successful
64 async testConnection() {
66 const pool = await this.getPool();
67 await pool.query('SELECT 1');
68 console.log('[WHMCS MySQL] ✅ Database connection successful');
71 console.error('[WHMCS MySQL] ❌ Database connection failed:', error.message);
77 * Get all clients (customers).
78 * @param {string} [status] - Filter by status (e.g., 'Active')
79 * @returns {Promise<Array>} Client records
81 async getClients(status = 'Active') {
83 ? 'SELECT * FROM tblclients WHERE status = ? ORDER BY id'
84 : 'SELECT * FROM tblclients ORDER BY id';
86 return await this.query(sql, status ? [status] : []);
91 * @param {number} clientId - Client ID
92 * @returns {Promise<object|null>} Client record or null
94 async getClientById(clientId) {
95 const sql = 'SELECT * FROM tblclients WHERE id = ?';
96 const rows = await this.query(sql, [clientId]);
97 return rows[0] || null;
102 * @returns {Promise<Array>} Domain records with client information
108 c.firstname, c.lastname, c.email, c.companyname
110 LEFT JOIN tblclients c ON d.userid = c.id
113 return await this.query(sql);
117 * Get domains for specific client.
118 * @param {number} clientId - Client ID
119 * @returns {Promise<Array>} Domain records
121 async getDomainsByClient(clientId) {
122 const sql = 'SELECT * FROM tbldomains WHERE userid = ? ORDER BY id';
123 return await this.query(sql, [clientId]);
127 * Get all hosting/products.
128 * @returns {Promise<Array>} Product records with client and product information
130 async getProducts() {
134 c.firstname, c.lastname, c.email, c.companyname,
135 p.name as product_name, p.type as product_type
137 LEFT JOIN tblclients c ON h.userid = c.id
138 LEFT JOIN tblproducts p ON h.packageid = p.id
141 return await this.query(sql);
145 * Get products for specific client.
146 * @param {number} clientId - Client ID
147 * @returns {Promise<Array>} Product records with product information
149 async getProductsByClient(clientId) {
153 p.name as product_name, p.type as product_type
155 LEFT JOIN tblproducts p ON h.packageid = p.id
159 return await this.query(sql, [clientId]);
164 * @returns {Promise<Array>} Invoice records with client information
166 async getInvoices() {
170 c.firstname, c.lastname, c.email, c.companyname
172 LEFT JOIN tblclients c ON i.userid = c.id
175 return await this.query(sql);
180 * @param {number} invoiceId - Invoice ID
181 * @returns {Promise<Array>} Invoice item records
183 async getInvoiceItems(invoiceId) {
184 const sql = 'SELECT * FROM tblinvoiceitems WHERE invoiceid = ? ORDER BY id';
185 return await this.query(sql, [invoiceId]);
190 * @param {string|null} [status] - Filter by status
191 * @returns {Promise<Array>} Ticket records
193 async getTickets(status = null) {
195 ? 'SELECT * FROM tbltickets WHERE status = ? ORDER BY id'
196 : 'SELECT * FROM tbltickets ORDER BY id';
198 return await this.query(sql, status ? [status] : []);
202 * Get ticket replies.
203 * @param {number} ticketId - Ticket ID
204 * @returns {Promise<Array>} Ticket reply records
206 async getTicketReplies(ticketId) {
207 const sql = 'SELECT * FROM tblticketreplies WHERE tid = ? ORDER BY id';
208 return await this.query(sql, [ticketId]);
213 * @returns {Promise<Array>} Custom field definitions
215 async getCustomFields() {
216 const sql = 'SELECT * FROM tblcustomfields WHERE type = "client" ORDER BY id';
217 return await this.query(sql);
221 * Get custom field values for client.
222 * @param {number} clientId - Client ID
223 * @returns {Promise<Array>} Custom field value records
225 async getCustomFieldValues(clientId) {
229 f.fieldname, f.fieldtype
230 FROM tblcustomfieldsvalues v
231 LEFT JOIN tblcustomfields f ON v.fieldid = f.id
232 WHERE v.relid = ? AND f.type = 'client'
234 return await this.query(sql, [clientId]);
238 * Get database statistics.
239 * @returns {Promise<object>} Statistics with counts for all entities
241 async getStatistics() {
244 stats.clients = await this.query('SELECT COUNT(*) as count FROM tblclients');
245 stats.activeClients = await this.query('SELECT COUNT(*) as count FROM tblclients WHERE status = "Active"');
246 stats.domains = await this.query('SELECT COUNT(*) as count FROM tbldomains');
247 stats.products = await this.query('SELECT COUNT(*) as count FROM tblhosting');
248 stats.invoices = await this.query('SELECT COUNT(*) as count FROM tblinvoices');
249 stats.tickets = await this.query('SELECT COUNT(*) as count FROM tbltickets');
252 totalClients: stats.clients[0].count,
253 activeClients: stats.activeClients[0].count,
254 totalDomains: stats.domains[0].count,
255 totalProducts: stats.products[0].count,
256 totalInvoices: stats.invoices[0].count,
257 totalTickets: stats.tickets[0].count
262 * Export all data for migration.
263 * @returns {Promise<object>} Complete export with clients, domains, products, etc.
265 async exportAllData() {
266 console.log('[WHMCS MySQL] Starting full data export...');
268 const stats = await this.getStatistics();
269 console.log('[WHMCS MySQL] Database Statistics:');
270 console.log(` - Total Clients: ${stats.totalClients}`);
271 console.log(` - Active Clients: ${stats.activeClients}`);
272 console.log(` - Domains: ${stats.totalDomains}`);
273 console.log(` - Products: ${stats.totalProducts}`);
274 console.log(` - Invoices: ${stats.totalInvoices}`);
275 console.log(` - Tickets: ${stats.totalTickets}\n`);
277 console.log('[WHMCS MySQL] Fetching clients...');
278 const clients = await this.getClients('Active');
279 console.log(`[WHMCS MySQL] Found ${clients.length} active clients`);
281 console.log('[WHMCS MySQL] Fetching domains...');
282 const domains = await this.getDomains();
283 console.log(`[WHMCS MySQL] Found ${domains.length} domains`);
285 console.log('[WHMCS MySQL] Fetching products...');
286 const products = await this.getProducts();
287 console.log(`[WHMCS MySQL] Found ${products.length} products`);
289 console.log('[WHMCS MySQL] Fetching custom fields...');
290 const customFields = await this.getCustomFields();
291 console.log(`[WHMCS MySQL] Found ${customFields.length} custom fields`);
299 exportedAt: new Date().toISOString()
304 * Close connection pool
308 await this.pool.end();
314module.exports = WHMCSMysqlService;