3 * Syncs domain data from all registrars (Cloudflare, Moniker, eNom) every 5 minutes
4 * Stores results in Redis for caching and updates PostgreSQL database
7const pool = require('./db');
8const cloudflare = require('./cloudflare');
9const moniker = require('./moniker');
10const enom = require('./enom');
11const { createClient } = require('redis');
13const SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes
14const REDIS_KEY_PREFIX = 'domains:sync:';
15const REDIS_CACHE_TTL = 600; // 10 minutes cache
17let redisClient = null;
18let syncInterval = null;
22 * Initialize Redis client
24async function initRedis() {
25 if (redisClient) return redisClient;
28 redisClient = createClient({
29 url: process.env.REDIS_URL || `redis://${process.env.REDIS_HOST || 'localhost'}:${process.env.REDIS_PORT || 6379}`,
30 username: process.env.REDIS_USERNAME,
31 password: process.env.REDIS_PASSWORD
34 redisClient.on('error', (err) => {
35 console.error('[Domain Sync] Redis error:', err);
38 await redisClient.connect();
39 console.log('[Domain Sync] Redis connected');
43 console.error('[Domain Sync] Failed to connect to Redis:', error.message);
49 * Sync domains from Cloudflare
51async function syncCloudflare() {
52 console.log('[Domain Sync] Syncing from Cloudflare...');
55 const zones = await cloudflare.listZones();
57 if (!zones || zones.length === 0) {
58 console.log('[Domain Sync] No Cloudflare zones found');
62 console.log(`[Domain Sync] Found ${zones.length} Cloudflare zones`);
64 const domains = zones.map(zone => ({
65 domain_name: zone.name,
66 registrar: 'cloudflare',
67 registrar_domain_id: zone.id,
68 status: zone.status === 'active' ? 'active' : 'pending',
69 nameservers: zone.name_servers || [],
70 created_at: zone.created_on,
71 modified_at: zone.modified_on,
77 await redisClient.setEx(
78 `${REDIS_KEY_PREFIX}cloudflare`,
80 JSON.stringify(domains)
86 console.error('[Domain Sync] Cloudflare sync error:', error.message);
92 * Sync domains from Moniker
94async function syncMoniker() {
95 console.log('[Domain Sync] Syncing from Moniker...');
97 // Check if Moniker credentials are configured
98 const isTestMode = !process.env.MONIKER_USER_KEY ||
99 process.env.MONIKER_USER_KEY === 'monikerapi' ||
100 process.env.MONIKER_BASE_URL?.includes('testapi');
103 console.log('[Domain Sync] Moniker in test mode - skipping sync');
108 // Moniker doesn't have a "list all domains" endpoint in standard API
109 // We'll need to query our database for domains registered via Moniker
110 // and update their details
111 const result = await pool.query(
112 `SELECT domain_id, domain_name, registrar_domain_id
114 WHERE registrar = 'moniker' AND status != 'cancelled'`
117 if (result.rows.length === 0) {
118 console.log('[Domain Sync] No Moniker domains to sync');
122 console.log(`[Domain Sync] Syncing ${result.rows.length} Moniker domains`);
126 for (const row of result.rows) {
128 const domainInfo = await moniker.getDomainInfo(row.domain_name);
131 domain_id: row.domain_id,
132 domain_name: row.domain_name,
133 registrar: 'moniker',
134 registrar_domain_id: domainInfo.domain_id || row.registrar_domain_id,
135 status: domainInfo.status?.toLowerCase() || 'active',
136 expiration_date: domainInfo.expiration_date,
137 nameservers: domainInfo.nameservers || [],
141 // Small delay to avoid rate limiting
142 await new Promise(resolve => setTimeout(resolve, 500));
144 console.error(`[Domain Sync] Error syncing Moniker domain ${row.domain_name}:`, error.message);
150 await redisClient.setEx(
151 `${REDIS_KEY_PREFIX}moniker`,
153 JSON.stringify(domains)
159 console.error('[Domain Sync] Moniker sync error:', error.message);
165 * Sync domains from eNom
167async function syncEnom() {
168 console.log('[Domain Sync] Syncing from eNom...');
170 // Check if eNom credentials are configured
171 if (!process.env.ENOM_UID || !process.env.ENOM_PASSWORD) {
172 console.log('[Domain Sync] eNom credentials not configured - skipping sync');
177 // eNom doesn't have a "list all domains" endpoint in standard API
178 // We'll query our database for eNom domains and update their details
179 const result = await pool.query(
180 `SELECT domain_id, domain_name, registrar_domain_id
182 WHERE registrar = 'enom' AND status != 'cancelled'`
185 if (result.rows.length === 0) {
186 console.log('[Domain Sync] No eNom domains to sync');
190 console.log(`[Domain Sync] Syncing ${result.rows.length} eNom domains`);
194 for (const row of result.rows) {
196 const domainInfo = await enom.getDomainInfo(row.domain_name);
199 domain_id: row.domain_id,
200 domain_name: row.domain_name,
202 registrar_domain_id: domainInfo.domain_id || row.registrar_domain_id,
203 status: domainInfo.status?.toLowerCase() || 'active',
204 expiration_date: domainInfo.expiration,
205 nameservers: domainInfo.nameservers || [],
209 // Small delay to avoid rate limiting
210 await new Promise(resolve => setTimeout(resolve, 500));
212 console.error(`[Domain Sync] Error syncing eNom domain ${row.domain_name}:`, error.message);
218 await redisClient.setEx(
219 `${REDIS_KEY_PREFIX}enom`,
221 JSON.stringify(domains)
227 console.error('[Domain Sync] eNom sync error:', error.message);
233 * Update database with synced domain data
236async function updateDatabase(domains) {
237 if (!domains || domains.length === 0) {
238 console.log('[Domain Sync] No domains to update in database');
245 for (const domain of domains) {
247 // Check if domain exists
248 const existing = await pool.query(
249 'SELECT domain_id FROM domains WHERE domain_name = $1',
253 if (existing.rows.length > 0) {
254 // Update existing domain
255 if (domain.domain_id) {
258 SET registrar_domain_id = COALESCE($1, registrar_domain_id),
259 status = COALESCE($2, status),
260 expiration_date = COALESCE($3, expiration_date),
261 nameservers = COALESCE($4, nameservers),
263 WHERE domain_id = $5`,
265 domain.registrar_domain_id,
267 domain.expiration_date,
268 JSON.stringify(domain.nameservers),
275 // Insert new domain (Cloudflare zones not in database yet)
276 if (domain.registrar === 'cloudflare') {
277 // Get default tenant_id (you may want to make this smarter)
278 const tenantResult = await pool.query('SELECT tenant_id FROM tenants LIMIT 1');
280 if (tenantResult.rows.length > 0) {
282 `INSERT INTO domains (
283 tenant_id, domain_name, registrar, registrar_domain_id,
284 status, nameservers, expiration_date, created_at, updated_at
286 VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
287 ON CONFLICT (domain_name) DO NOTHING`,
289 tenantResult.rows[0].tenant_id,
292 domain.registrar_domain_id,
294 JSON.stringify(domain.nameservers),
295 domain.expiration_date || new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // Default 1 year
303 console.error(`[Domain Sync] Error updating domain ${domain.domain_name}:`, error.message);
307 console.log(`[Domain Sync] Database updated: ${updated} updated, ${inserted} inserted`);
311 * Perform full sync from all registrars
313async function performSync() {
314 const startTime = Date.now();
315 console.log(`[Domain Sync] Starting full sync at ${new Date().toISOString()}`);
318 // Sync from all registrars in parallel
319 const [cloudflareDomains, monikerDomains, enomDomains] = await Promise.all([
325 const allDomains = [...cloudflareDomains, ...monikerDomains, ...enomDomains];
327 console.log(`[Domain Sync] Synced ${allDomains.length} total domains (Cloudflare: ${cloudflareDomains.length}, Moniker: ${monikerDomains.length}, eNom: ${enomDomains.length})`);
330 await updateDatabase(allDomains);
332 // Store summary in Redis
335 last_sync: new Date().toISOString(),
336 duration_ms: Date.now() - startTime,
337 total_domains: allDomains.length,
339 cloudflare: cloudflareDomains.length,
340 moniker: monikerDomains.length,
341 enom: enomDomains.length
345 await redisClient.setEx(
346 `${REDIS_KEY_PREFIX}summary`,
348 JSON.stringify(summary)
352 lastSyncTime = Date.now();
353 console.log(`[Domain Sync] Sync completed in ${Date.now() - startTime}ms`);
357 total: allDomains.length,
358 duration: Date.now() - startTime
361 console.error('[Domain Sync] Sync failed:', error);
370 * Start periodic sync
372async function startPeriodicSync() {
373 console.log('[Domain Sync] Starting periodic sync service...');
381 // Set up periodic sync
382 syncInterval = setInterval(async () => {
386 console.log(`[Domain Sync] Periodic sync started (interval: ${SYNC_INTERVAL / 1000}s)`);
392function stopPeriodicSync() {
394 clearInterval(syncInterval);
396 console.log('[Domain Sync] Periodic sync stopped');
401 * Get cached domains from Redis
404async function getCachedDomains(registrar = null) {
415 const cached = await redisClient.get(`${REDIS_KEY_PREFIX}${registrar}`);
416 return cached ? JSON.parse(cached) : null;
419 const [cloudflare, moniker, enom] = await Promise.all([
420 redisClient.get(`${REDIS_KEY_PREFIX}cloudflare`),
421 redisClient.get(`${REDIS_KEY_PREFIX}moniker`),
422 redisClient.get(`${REDIS_KEY_PREFIX}enom`)
426 cloudflare: cloudflare ? JSON.parse(cloudflare) : [],
427 moniker: moniker ? JSON.parse(moniker) : [],
428 enom: enom ? JSON.parse(enom) : []
432 console.error('[Domain Sync] Error getting cached domains:', error.message);
440async function getSyncSummary() {
450 const summary = await redisClient.get(`${REDIS_KEY_PREFIX}summary`);
451 return summary ? JSON.parse(summary) : null;
453 console.error('[Domain Sync] Error getting sync summary:', error.message);
459 * Force immediate sync
461async function forceSyncNow() {
462 console.log('[Domain Sync] Force sync requested');
463 return await performSync();