2 * DNS Records Sync Worker
3 * Syncs DNS records from Cloudflare to Redis cache every 5 minutes
4 * Provides fast page loads and reduces Cloudflare API calls
5 * Cache is invalidated when users edit DNS records for instant updates
8const Redis = require('ioredis');
9const pool = require('./db');
10const { listDNSRecords } = require('./services/cloudflare');
12const redisConfig = require('./config/redis');
13let redisClient = null;
15const SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes
16const REDIS_KEY_PREFIX_RECORDS = 'dns:records:';
17const REDIS_KEY_PREFIX_SYNC = 'dns:sync:';
18const API_DELAY = 500; // Delay between API calls to avoid rate limiting
21 * Initialize Redis connection
25 redisClient = new Redis({
27 maxRetriesPerRequest: null,
31 redisClient.on('error', (err) => {
32 console.error('[DNS Sync] Redis error:', err.message);
35 redisClient.on('connect', () => {
36 console.log('[DNS Sync] Redis connected');
39 return redisClient.connect().catch(err => {
40 console.error('[DNS Sync] Redis connection failed:', err.message);
45 * Get all domains with Cloudflare zone IDs from database
47async function getDomainsWithZones() {
49 const result = await pool.query(`
55 metadata->>'cloudflare_zone_id'
59 (registrar = 'cloudflare' OR metadata->>'has_cloudflare_dns' = 'true')
61 registrar_domain_id IS NOT NULL
62 OR metadata->>'zone_id' IS NOT NULL
63 OR metadata->>'cloudflare_zone_id' IS NOT NULL
68 return result.rows.filter(row => row.zone_id);
70 console.error('[DNS Sync] Error fetching domains:', error.message);
76 * Sync DNS records for a single domain
80async function syncDomainRecords(domain, zoneId) {
82 const records = await listDNSRecords(zoneId);
84 if (!records || records.length === 0) {
85 console.log(`[DNS Sync] ${domain}: No DNS records found`);
86 return { domain, records: [], error: null };
89 // Store records in Redis
90 const recordsKey = REDIS_KEY_PREFIX_RECORDS + domain;
91 const syncKey = REDIS_KEY_PREFIX_SYNC + domain;
92 const timestamp = new Date().toISOString();
94 await redisClient.setex(
96 SYNC_INTERVAL * 2, // 10 minutes TTL (2x sync interval for safety)
97 JSON.stringify(records)
100 await redisClient.setex(
106 console.log(`[DNS Sync] ${domain}: Cached ${records.length} records`);
107 return { domain, records, error: null };
109 console.error(`[DNS Sync] ${domain}: Error syncing records:`, error.message);
110 return { domain, records: [], error: error.message };
115 * Sync all DNS records
117async function syncAllDNSRecords() {
118 const startTime = Date.now();
119 console.log('[DNS Sync] Starting DNS records sync...');
122 // Get all domains with Cloudflare zones
123 const domains = await getDomainsWithZones();
125 if (domains.length === 0) {
126 console.log('[DNS Sync] No domains with Cloudflare zones found');
130 console.log(`[DNS Sync] Found ${domains.length} domains with Cloudflare zones`);
132 let successCount = 0;
134 let totalRecords = 0;
136 // Sync each domain with rate limiting
137 for (const { domain_name, zone_id } of domains) {
138 // Rate limiting delay
139 await new Promise(resolve => setTimeout(resolve, API_DELAY));
141 const result = await syncDomainRecords(domain_name, zone_id);
147 totalRecords += result.records.length;
151 const elapsed = Date.now() - startTime;
152 console.log(`[DNS Sync] Completed: ${successCount} domains synced, ${errorCount} errors, ${totalRecords} total records (${elapsed}ms)`);
154 console.error('[DNS Sync] Sync error:', error);
159 * Get cached DNS records for a domain
160 * Returns null if not in cache
163async function getCachedDNSRecords(domain) {
169 const recordsKey = REDIS_KEY_PREFIX_RECORDS + domain;
170 const syncKey = REDIS_KEY_PREFIX_SYNC + domain;
172 const [recordsJson, syncTime] = await Promise.all([
173 redisClient.get(recordsKey),
174 redisClient.get(syncKey)
182 records: JSON.parse(recordsJson),
183 last_synced: syncTime
186 console.error(`[DNS Sync] Error getting cached records for ${domain}:`, error.message);
192 * Invalidate cache for a domain (call this after DNS edits)
195async function invalidateDNSCache(domain) {
201 const recordsKey = REDIS_KEY_PREFIX_RECORDS + domain;
202 const syncKey = REDIS_KEY_PREFIX_SYNC + domain;
205 redisClient.del(recordsKey),
206 redisClient.del(syncKey)
209 console.log(`[DNS Sync] Cache invalidated for ${domain}`);
211 console.error(`[DNS Sync] Error invalidating cache for ${domain}:`, error.message);
216 * Start the DNS sync worker
218function startDNSSyncWorker() {
219 console.log(`[DNS Sync] Worker started - syncing every ${SYNC_INTERVAL / 1000 / 60} minutes`);
220 console.log('[DNS Sync] Architecture:');
221 console.log('[DNS Sync] 1. Fetch DNS records from Cloudflare for all domains');
222 console.log('[DNS Sync] 2. Cache records in Redis (10 min TTL)');
223 console.log('[DNS Sync] 3. DNS edits invalidate cache for instant updates');
225 // Initialize Redis and run first sync
228 console.log('[DNS Sync] Running initial DNS sync...');
229 return syncAllDNSRecords();
232 // Schedule periodic sync
233 setInterval(syncAllDNSRecords, SYNC_INTERVAL);
234 console.log('[DNS Sync] Periodic DNS sync scheduled');
237 console.error('[DNS Sync] Startup error:', err);
241// Start the worker if this file is run directly
242if (require.main === module) {
243 startDNSSyncWorker();