EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
dnsRecordsSyncWorker.js
Go to the documentation of this file.
1/**
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
6 */
7
8const Redis = require('ioredis');
9const pool = require('./db');
10const { listDNSRecords } = require('./services/cloudflare');
11
12const redisConfig = require('./config/redis');
13let redisClient = null;
14
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
19
20/**
21 * Initialize Redis connection
22 */
23function initRedis() {
24 if (!redisClient) {
25 redisClient = new Redis({
26 ...redisConfig,
27 maxRetriesPerRequest: null,
28 lazyConnect: true
29 });
30
31 redisClient.on('error', (err) => {
32 console.error('[DNS Sync] Redis error:', err.message);
33 });
34
35 redisClient.on('connect', () => {
36 console.log('[DNS Sync] Redis connected');
37 });
38 }
39 return redisClient.connect().catch(err => {
40 console.error('[DNS Sync] Redis connection failed:', err.message);
41 });
42}
43
44/**
45 * Get all domains with Cloudflare zone IDs from database
46 */
47async function getDomainsWithZones() {
48 try {
49 const result = await pool.query(`
50 SELECT
51 domain_name,
52 COALESCE(
53 registrar_domain_id,
54 metadata->>'zone_id',
55 metadata->>'cloudflare_zone_id'
56 ) as zone_id
57 FROM domains
58 WHERE
59 (registrar = 'cloudflare' OR metadata->>'has_cloudflare_dns' = 'true')
60 AND (
61 registrar_domain_id IS NOT NULL
62 OR metadata->>'zone_id' IS NOT NULL
63 OR metadata->>'cloudflare_zone_id' IS NOT NULL
64 )
65 ORDER BY domain_name
66 `);
67
68 return result.rows.filter(row => row.zone_id);
69 } catch (error) {
70 console.error('[DNS Sync] Error fetching domains:', error.message);
71 return [];
72 }
73}
74
75/**
76 * Sync DNS records for a single domain
77 * @param domain
78 * @param zoneId
79 */
80async function syncDomainRecords(domain, zoneId) {
81 try {
82 const records = await listDNSRecords(zoneId);
83
84 if (!records || records.length === 0) {
85 console.log(`[DNS Sync] ${domain}: No DNS records found`);
86 return { domain, records: [], error: null };
87 }
88
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();
93
94 await redisClient.setex(
95 recordsKey,
96 SYNC_INTERVAL * 2, // 10 minutes TTL (2x sync interval for safety)
97 JSON.stringify(records)
98 );
99
100 await redisClient.setex(
101 syncKey,
102 SYNC_INTERVAL * 2,
103 timestamp
104 );
105
106 console.log(`[DNS Sync] ${domain}: Cached ${records.length} records`);
107 return { domain, records, error: null };
108 } catch (error) {
109 console.error(`[DNS Sync] ${domain}: Error syncing records:`, error.message);
110 return { domain, records: [], error: error.message };
111 }
112}
113
114/**
115 * Sync all DNS records
116 */
117async function syncAllDNSRecords() {
118 const startTime = Date.now();
119 console.log('[DNS Sync] Starting DNS records sync...');
120
121 try {
122 // Get all domains with Cloudflare zones
123 const domains = await getDomainsWithZones();
124
125 if (domains.length === 0) {
126 console.log('[DNS Sync] No domains with Cloudflare zones found');
127 return;
128 }
129
130 console.log(`[DNS Sync] Found ${domains.length} domains with Cloudflare zones`);
131
132 let successCount = 0;
133 let errorCount = 0;
134 let totalRecords = 0;
135
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));
140
141 const result = await syncDomainRecords(domain_name, zone_id);
142
143 if (result.error) {
144 errorCount++;
145 } else {
146 successCount++;
147 totalRecords += result.records.length;
148 }
149 }
150
151 const elapsed = Date.now() - startTime;
152 console.log(`[DNS Sync] Completed: ${successCount} domains synced, ${errorCount} errors, ${totalRecords} total records (${elapsed}ms)`);
153 } catch (error) {
154 console.error('[DNS Sync] Sync error:', error);
155 }
156}
157
158/**
159 * Get cached DNS records for a domain
160 * Returns null if not in cache
161 * @param domain
162 */
163async function getCachedDNSRecords(domain) {
164 if (!redisClient) {
165 await initRedis();
166 }
167
168 try {
169 const recordsKey = REDIS_KEY_PREFIX_RECORDS + domain;
170 const syncKey = REDIS_KEY_PREFIX_SYNC + domain;
171
172 const [recordsJson, syncTime] = await Promise.all([
173 redisClient.get(recordsKey),
174 redisClient.get(syncKey)
175 ]);
176
177 if (!recordsJson) {
178 return null;
179 }
180
181 return {
182 records: JSON.parse(recordsJson),
183 last_synced: syncTime
184 };
185 } catch (error) {
186 console.error(`[DNS Sync] Error getting cached records for ${domain}:`, error.message);
187 return null;
188 }
189}
190
191/**
192 * Invalidate cache for a domain (call this after DNS edits)
193 * @param domain
194 */
195async function invalidateDNSCache(domain) {
196 if (!redisClient) {
197 await initRedis();
198 }
199
200 try {
201 const recordsKey = REDIS_KEY_PREFIX_RECORDS + domain;
202 const syncKey = REDIS_KEY_PREFIX_SYNC + domain;
203
204 await Promise.all([
205 redisClient.del(recordsKey),
206 redisClient.del(syncKey)
207 ]);
208
209 console.log(`[DNS Sync] Cache invalidated for ${domain}`);
210 } catch (error) {
211 console.error(`[DNS Sync] Error invalidating cache for ${domain}:`, error.message);
212 }
213}
214
215/**
216 * Start the DNS sync worker
217 */
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');
224
225 // Initialize Redis and run first sync
226 initRedis()
227 .then(() => {
228 console.log('[DNS Sync] Running initial DNS sync...');
229 return syncAllDNSRecords();
230 })
231 .then(() => {
232 // Schedule periodic sync
233 setInterval(syncAllDNSRecords, SYNC_INTERVAL);
234 console.log('[DNS Sync] Periodic DNS sync scheduled');
235 })
236 .catch(err => {
237 console.error('[DNS Sync] Startup error:', err);
238 });
239}
240
241// Start the worker if this file is run directly
242if (require.main === module) {
243 startDNSSyncWorker();
244}
245
246module.exports = {
247 startDNSSyncWorker,
248 syncAllDNSRecords,
249 getCachedDNSRecords,
250 invalidateDNSCache
251};