EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
domainSync.js
Go to the documentation of this file.
1/**
2 * Domain Sync Service
3 * Syncs domain data from all registrars (Cloudflare, Moniker, eNom) every 5 minutes
4 * Stores results in Redis for caching and updates PostgreSQL database
5 */
6
7const pool = require('./db');
8const cloudflare = require('./cloudflare');
9const moniker = require('./moniker');
10const enom = require('./enom');
11const { createClient } = require('redis');
12
13const SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes
14const REDIS_KEY_PREFIX = 'domains:sync:';
15const REDIS_CACHE_TTL = 600; // 10 minutes cache
16
17let redisClient = null;
18let syncInterval = null;
19let lastSyncTime = 0;
20
21/**
22 * Initialize Redis client
23 */
24async function initRedis() {
25 if (redisClient) return redisClient;
26
27 try {
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
32 });
33
34 redisClient.on('error', (err) => {
35 console.error('[Domain Sync] Redis error:', err);
36 });
37
38 await redisClient.connect();
39 console.log('[Domain Sync] Redis connected');
40
41 return redisClient;
42 } catch (error) {
43 console.error('[Domain Sync] Failed to connect to Redis:', error.message);
44 return null;
45 }
46}
47
48/**
49 * Sync domains from Cloudflare
50 */
51async function syncCloudflare() {
52 console.log('[Domain Sync] Syncing from Cloudflare...');
53
54 try {
55 const zones = await cloudflare.listZones();
56
57 if (!zones || zones.length === 0) {
58 console.log('[Domain Sync] No Cloudflare zones found');
59 return [];
60 }
61
62 console.log(`[Domain Sync] Found ${zones.length} Cloudflare zones`);
63
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,
72 raw_data: zone
73 }));
74
75 // Cache in Redis
76 if (redisClient) {
77 await redisClient.setEx(
78 `${REDIS_KEY_PREFIX}cloudflare`,
79 REDIS_CACHE_TTL,
80 JSON.stringify(domains)
81 );
82 }
83
84 return domains;
85 } catch (error) {
86 console.error('[Domain Sync] Cloudflare sync error:', error.message);
87 return [];
88 }
89}
90
91/**
92 * Sync domains from Moniker
93 */
94async function syncMoniker() {
95 console.log('[Domain Sync] Syncing from Moniker...');
96
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');
101
102 if (isTestMode) {
103 console.log('[Domain Sync] Moniker in test mode - skipping sync');
104 return [];
105 }
106
107 try {
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
113 FROM domains
114 WHERE registrar = 'moniker' AND status != 'cancelled'`
115 );
116
117 if (result.rows.length === 0) {
118 console.log('[Domain Sync] No Moniker domains to sync');
119 return [];
120 }
121
122 console.log(`[Domain Sync] Syncing ${result.rows.length} Moniker domains`);
123
124 const domains = [];
125
126 for (const row of result.rows) {
127 try {
128 const domainInfo = await moniker.getDomainInfo(row.domain_name);
129
130 domains.push({
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 || [],
138 raw_data: domainInfo
139 });
140
141 // Small delay to avoid rate limiting
142 await new Promise(resolve => setTimeout(resolve, 500));
143 } catch (error) {
144 console.error(`[Domain Sync] Error syncing Moniker domain ${row.domain_name}:`, error.message);
145 }
146 }
147
148 // Cache in Redis
149 if (redisClient) {
150 await redisClient.setEx(
151 `${REDIS_KEY_PREFIX}moniker`,
152 REDIS_CACHE_TTL,
153 JSON.stringify(domains)
154 );
155 }
156
157 return domains;
158 } catch (error) {
159 console.error('[Domain Sync] Moniker sync error:', error.message);
160 return [];
161 }
162}
163
164/**
165 * Sync domains from eNom
166 */
167async function syncEnom() {
168 console.log('[Domain Sync] Syncing from eNom...');
169
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');
173 return [];
174 }
175
176 try {
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
181 FROM domains
182 WHERE registrar = 'enom' AND status != 'cancelled'`
183 );
184
185 if (result.rows.length === 0) {
186 console.log('[Domain Sync] No eNom domains to sync');
187 return [];
188 }
189
190 console.log(`[Domain Sync] Syncing ${result.rows.length} eNom domains`);
191
192 const domains = [];
193
194 for (const row of result.rows) {
195 try {
196 const domainInfo = await enom.getDomainInfo(row.domain_name);
197
198 domains.push({
199 domain_id: row.domain_id,
200 domain_name: row.domain_name,
201 registrar: 'enom',
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 || [],
206 raw_data: domainInfo
207 });
208
209 // Small delay to avoid rate limiting
210 await new Promise(resolve => setTimeout(resolve, 500));
211 } catch (error) {
212 console.error(`[Domain Sync] Error syncing eNom domain ${row.domain_name}:`, error.message);
213 }
214 }
215
216 // Cache in Redis
217 if (redisClient) {
218 await redisClient.setEx(
219 `${REDIS_KEY_PREFIX}enom`,
220 REDIS_CACHE_TTL,
221 JSON.stringify(domains)
222 );
223 }
224
225 return domains;
226 } catch (error) {
227 console.error('[Domain Sync] eNom sync error:', error.message);
228 return [];
229 }
230}
231
232/**
233 * Update database with synced domain data
234 * @param domains
235 */
236async function updateDatabase(domains) {
237 if (!domains || domains.length === 0) {
238 console.log('[Domain Sync] No domains to update in database');
239 return;
240 }
241
242 let updated = 0;
243 let inserted = 0;
244
245 for (const domain of domains) {
246 try {
247 // Check if domain exists
248 const existing = await pool.query(
249 'SELECT domain_id FROM domains WHERE domain_name = $1',
250 [domain.domain_name]
251 );
252
253 if (existing.rows.length > 0) {
254 // Update existing domain
255 if (domain.domain_id) {
256 await pool.query(
257 `UPDATE domains
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),
262 updated_at = NOW()
263 WHERE domain_id = $5`,
264 [
265 domain.registrar_domain_id,
266 domain.status,
267 domain.expiration_date,
268 JSON.stringify(domain.nameservers),
269 domain.domain_id
270 ]
271 );
272 updated++;
273 }
274 } else {
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');
279
280 if (tenantResult.rows.length > 0) {
281 await pool.query(
282 `INSERT INTO domains (
283 tenant_id, domain_name, registrar, registrar_domain_id,
284 status, nameservers, expiration_date, created_at, updated_at
285 )
286 VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
287 ON CONFLICT (domain_name) DO NOTHING`,
288 [
289 tenantResult.rows[0].tenant_id,
290 domain.domain_name,
291 domain.registrar,
292 domain.registrar_domain_id,
293 domain.status,
294 JSON.stringify(domain.nameservers),
295 domain.expiration_date || new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // Default 1 year
296 ]
297 );
298 inserted++;
299 }
300 }
301 }
302 } catch (error) {
303 console.error(`[Domain Sync] Error updating domain ${domain.domain_name}:`, error.message);
304 }
305 }
306
307 console.log(`[Domain Sync] Database updated: ${updated} updated, ${inserted} inserted`);
308}
309
310/**
311 * Perform full sync from all registrars
312 */
313async function performSync() {
314 const startTime = Date.now();
315 console.log(`[Domain Sync] Starting full sync at ${new Date().toISOString()}`);
316
317 try {
318 // Sync from all registrars in parallel
319 const [cloudflareDomains, monikerDomains, enomDomains] = await Promise.all([
320 syncCloudflare(),
321 syncMoniker(),
322 syncEnom()
323 ]);
324
325 const allDomains = [...cloudflareDomains, ...monikerDomains, ...enomDomains];
326
327 console.log(`[Domain Sync] Synced ${allDomains.length} total domains (Cloudflare: ${cloudflareDomains.length}, Moniker: ${monikerDomains.length}, eNom: ${enomDomains.length})`);
328
329 // Update database
330 await updateDatabase(allDomains);
331
332 // Store summary in Redis
333 if (redisClient) {
334 const summary = {
335 last_sync: new Date().toISOString(),
336 duration_ms: Date.now() - startTime,
337 total_domains: allDomains.length,
338 by_registrar: {
339 cloudflare: cloudflareDomains.length,
340 moniker: monikerDomains.length,
341 enom: enomDomains.length
342 }
343 };
344
345 await redisClient.setEx(
346 `${REDIS_KEY_PREFIX}summary`,
347 REDIS_CACHE_TTL,
348 JSON.stringify(summary)
349 );
350 }
351
352 lastSyncTime = Date.now();
353 console.log(`[Domain Sync] Sync completed in ${Date.now() - startTime}ms`);
354
355 return {
356 success: true,
357 total: allDomains.length,
358 duration: Date.now() - startTime
359 };
360 } catch (error) {
361 console.error('[Domain Sync] Sync failed:', error);
362 return {
363 success: false,
364 error: error.message
365 };
366 }
367}
368
369/**
370 * Start periodic sync
371 */
372async function startPeriodicSync() {
373 console.log('[Domain Sync] Starting periodic sync service...');
374
375 // Initialize Redis
376 await initRedis();
377
378 // Do initial sync
379 await performSync();
380
381 // Set up periodic sync
382 syncInterval = setInterval(async () => {
383 await performSync();
384 }, SYNC_INTERVAL);
385
386 console.log(`[Domain Sync] Periodic sync started (interval: ${SYNC_INTERVAL / 1000}s)`);
387}
388
389/**
390 * Stop periodic sync
391 */
392function stopPeriodicSync() {
393 if (syncInterval) {
394 clearInterval(syncInterval);
395 syncInterval = null;
396 console.log('[Domain Sync] Periodic sync stopped');
397 }
398}
399
400/**
401 * Get cached domains from Redis
402 * @param registrar
403 */
404async function getCachedDomains(registrar = null) {
405 if (!redisClient) {
406 await initRedis();
407 }
408
409 if (!redisClient) {
410 return null;
411 }
412
413 try {
414 if (registrar) {
415 const cached = await redisClient.get(`${REDIS_KEY_PREFIX}${registrar}`);
416 return cached ? JSON.parse(cached) : null;
417 } else {
418 // Get all
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`)
423 ]);
424
425 return {
426 cloudflare: cloudflare ? JSON.parse(cloudflare) : [],
427 moniker: moniker ? JSON.parse(moniker) : [],
428 enom: enom ? JSON.parse(enom) : []
429 };
430 }
431 } catch (error) {
432 console.error('[Domain Sync] Error getting cached domains:', error.message);
433 return null;
434 }
435}
436
437/**
438 * Get sync summary
439 */
440async function getSyncSummary() {
441 if (!redisClient) {
442 await initRedis();
443 }
444
445 if (!redisClient) {
446 return null;
447 }
448
449 try {
450 const summary = await redisClient.get(`${REDIS_KEY_PREFIX}summary`);
451 return summary ? JSON.parse(summary) : null;
452 } catch (error) {
453 console.error('[Domain Sync] Error getting sync summary:', error.message);
454 return null;
455 }
456}
457
458/**
459 * Force immediate sync
460 */
461async function forceSyncNow() {
462 console.log('[Domain Sync] Force sync requested');
463 return await performSync();
464}
465
466module.exports = {
467 startPeriodicSync,
468 stopPeriodicSync,
469 performSync,
470 forceSyncNow,
471 getCachedDomains,
472 getSyncSummary,
473 initRedis
474};