4 * Import domains from WHMCS export to PostgreSQL
5 * Links domains to imported customers in the Precise PCs tenant
6 * Imports ALL domains, even if not registered through us
9require('dotenv').config();
10const fs = require('fs');
11const path = require('path');
12const pool = require('../services/db');
14const EXPORT_DIR = path.join(__dirname, '../exports');
16// Get tenant ID from Precise PCs tenant file
20function getTenantId() {
21 const tenantFilePath = path.join(EXPORT_DIR, 'precise_pcs_tenant.json');
23 if (fs.existsSync(tenantFilePath)) {
25 const tenantData = JSON.parse(fs.readFileSync(tenantFilePath, 'utf8'));
26 console.log(`š Using tenant: ${tenantData.name} (${tenantData.tenant_id})`);
27 return tenantData.tenant_id;
29 console.warn('ā ļø Could not read tenant file, using default');
33 return process.env.TENANT_ID || '00000000-0000-0000-0000-000000000001';
37 * Map WHMCS status to our domain status
40function mapDomainStatus(whmcsStatus) {
44 'Pending Transfer': 'pending_transfer',
46 'Cancelled': 'cancelled',
48 'Transferred Away': 'transferred_out',
49 'Redemption': 'redemption'
52 return statusMap[whmcsStatus] || 'unknown';
56 * Parse date from WHMCS format
59function parseDate(dateString) {
60 if (!dateString || dateString === 'Invalid Date') {
63 const date = new Date(dateString);
64 return isNaN(date.getTime()) ? null : date.toISOString();
68 * Create customer lookup map from WHMCS user ID to our customer ID
71async function createCustomerLookup(tenantId) {
72 const importLogPath = path.join(EXPORT_DIR, 'customer_import_log.json');
74 if (fs.existsSync(importLogPath)) {
75 const log = JSON.parse(fs.readFileSync(importLogPath, 'utf8'));
78 // Handle both array format and object with 'imported' property
79 const entries = Array.isArray(log) ? log : (log.imported || []);
81 for (const entry of entries) {
82 if (entry.whmcs_id && entry.postgres_id) {
83 lookup[entry.whmcs_id] = entry.postgres_id;
84 } else if (entry.whmcs_id && entry.customer_id) {
85 lookup[entry.whmcs_id] = entry.customer_id;
89 console.log(`š Customer lookup: ${Object.keys(lookup).length} mappings loaded`);
93 // Fallback: query database for customers with WHMCS IDs in notes
94 const result = await pool.query(
95 `SELECT customer_id, notes FROM customers WHERE tenant_id = $1 AND notes LIKE '%WHMCS ID:%'`,
100 for (const row of result.rows) {
101 const match = row.notes.match(/WHMCS ID:\s*(\d+)/);
103 lookup[parseInt(match[1])] = row.customer_id;
107 console.log(`š Customer lookup from database: ${Object.keys(lookup).length} mappings found`);
112 * Import domains from WHMCS data
114async function importDomains() {
115 const tenantId = getTenantId();
118 console.log('========================================');
119 console.log('WHMCS Domain Import');
120 console.log('========================================\n');
122 // Check if export file exists
123 const jsonPath = path.join(EXPORT_DIR, 'whmcs_export.json');
124 if (!fs.existsSync(jsonPath)) {
125 throw new Error(`Export file not found: ${jsonPath}\nRun: node migration-scripts/fetch_whmcs_data.js`);
129 const exportData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
130 const domains = exportData.domains || [];
132 console.log(`Found ${domains.length} domains to import`);
133 console.log(`Target tenant: ${tenantId}\n`);
135 // Create customer lookup
136 const customerLookup = await createCustomerLookup(tenantId);
138 console.log('Starting domain import...\n');
145 imported_at: new Date().toISOString(),
151 for (const domain of domains) {
153 // Skip if no domain name
154 if (!domain.domain || domain.domain.trim() === '') {
156 importLog.skipped.push({
158 reason: 'No domain name'
164 const customerId = customerLookup[domain.userid];
166 console.log(`ā ļø Skipping ${domain.domain}: Customer not found (WHMCS user ${domain.userid})`);
168 importLog.skipped.push({
170 domain: domain.domain,
171 whmcs_user_id: domain.userid,
172 reason: 'Customer not found in system'
177 // Check if domain already exists
178 const existing = await pool.query(
179 'SELECT domain_id FROM domains WHERE tenant_id = $1 AND domain_name = $2',
180 [tenantId, domain.domain.toLowerCase()]
183 if (existing.rows.length > 0) {
184 console.log(`āļø Skipping ${domain.domain}: Already exists`);
186 importLog.skipped.push({
188 domain: domain.domain,
189 reason: 'Already exists'
194 // Prepare domain data
197 customer_id: customerId,
198 domain_name: domain.domain.toLowerCase(),
199 registrar: domain.registrar || 'unknown',
200 registrar_domain_id: domain.id.toString(),
201 status: mapDomainStatus(domain.status),
202 registration_date: parseDate(domain.registrationdate),
203 expiration_date: parseDate(domain.expirydate),
204 auto_renew: domain.donotrenew ? false : true,
205 whois_privacy: domain.idprotection ? true : false,
206 renewal_price: parseFloat(domain.recurringamount) || 0,
208 notes: `Migrated from WHMCS\nWHMCS Domain ID: ${domain.id}\nRegistration Type: ${domain.type || 'Unknown'}\nPayment Method: ${domain.paymentmethod || 'N/A'}`
212 const insertQuery = `
213 INSERT INTO domains (
214 tenant_id, customer_id, domain_name, registrar, registrar_domain_id,
215 status, registration_date, expiration_date, auto_renew, whois_privacy,
216 renewal_price, currency, notes, created_at, updated_at
218 $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, NOW(), NOW()
220 RETURNING domain_id, domain_name, status
223 const result = await pool.query(insertQuery, [
224 domainData.tenant_id,
225 domainData.customer_id,
226 domainData.domain_name,
227 domainData.registrar,
228 domainData.registrar_domain_id,
230 domainData.registration_date,
231 domainData.expiration_date,
232 domainData.auto_renew,
233 domainData.whois_privacy,
234 domainData.renewal_price,
239 const insertedDomain = result.rows[0];
241 // Format expiry date for display
242 const expiryDisplay = domain.expirydate && domain.expirydate !== 'Invalid Date'
243 ? new Date(domain.expirydate).toLocaleDateString()
246 console.log(`ā
Imported: ${insertedDomain.domain_name} (${insertedDomain.status}) - Expires: ${expiryDisplay}`);
249 importLog.imported.push({
250 domain_id: insertedDomain.domain_id,
251 domain_name: insertedDomain.domain_name,
253 whmcs_user_id: domain.userid,
254 customer_id: customerId,
255 status: insertedDomain.status,
256 registrar: domainData.registrar,
257 expiration_date: domainData.expiration_date
261 console.error(`ā Error importing ${domain.domain}:`, error.message);
263 importLog.errors.push({
265 domain: domain.domain,
272 const logPath = path.join(EXPORT_DIR, 'domain_import_log.json');
273 fs.writeFileSync(logPath, JSON.stringify(importLog, null, 2));
275 console.log('\n========================================');
276 console.log('Domain Import Summary');
277 console.log('========================================');
278 console.log(`ā
Imported: ${imported}`);
279 console.log(`āļø Skipped: ${skipped}`);
280 console.log(`ā Errors: ${errors}`);
281 console.log('\nImport log saved:', logPath);
282 console.log('========================================\n');
284 // Show import status by domain status
286 const statusCounts = {};
287 for (const entry of importLog.imported) {
288 statusCounts[entry.status] = (statusCounts[entry.status] || 0) + 1;
291 console.log('Domains by status:');
292 for (const [status, count] of Object.entries(statusCounts)) {
293 console.log(` ${status}: ${count}`);
298 console.log('Next steps:');
299 console.log('1. Review domains in dashboard');
300 console.log('2. Verify domain-customer relationships');
301 console.log('3. Set up domain renewal notifications');
302 console.log('4. Import hosting products/contracts');
305 console.error('ā Domain import failed:', error.message);
312// Run if executed directly
313if (require.main === module) {
316 console.log('\nā
Domain import complete');
320 console.error('\nā Domain import failed:', error);
325module.exports = { importDomains };