1// backend/services/cloudflare.js
2const axios = require('axios');
4const CLOUDFLARE_BASE_URL = process.env.CLOUDFLARE_BASE_URL || 'https://api.cloudflare.com/client/v4';
5const CLOUDFLARE_ZONE_ID = process.env.CLOUDFLARE_ZONE_ID;
6const CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN || process.env.CLOUDFLARE_TOKEN;
7const CLOUDFLARE_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
9// ============================================================================
11// ============================================================================
13// Create DNS A or CNAME record for new tenant
19async function createSubdomain(subdomain, target) {
20 if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_TOKEN) {
21 throw new Error('Cloudflare credentials not configured');
24 const dnsName = `${subdomain}.everydaytech.au`;
27 const res = await axios.post(
28 `${CLOUDFLARE_BASE_URL}/zones/${CLOUDFLARE_ZONE_ID}/dns_records`,
32 content: target, // e.g. "app.everydaytech.au"
38 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
39 'Content-Type': 'application/json'
44 if (res.data.success) {
45 console.log(`[Cloudflare] Created subdomain ${dnsName} → ${target}`);
48 console.error('[Cloudflare] Failed:', res.data.errors);
49 throw new Error('Cloudflare DNS creation failed');
52 // Check for duplicate record error
53 const message = err.response?.data?.errors?.[0]?.message || err.message;
54 if (message && message.includes('record already exists')) {
55 console.warn(`[Cloudflare] Subdomain ${dnsName} already exists.`);
56 const error = new Error('Subdomain already exists');
57 error.code = 'SUBDOMAIN_EXISTS';
60 console.error('[Cloudflare] Error creating subdomain:', err.response?.data || err.message);
65// ============================================================================
66// Domain Registrar API
67// ============================================================================
70 * Search for domain availability on Cloudflare Registrar
71 * @param {string} domain - Domain name to search (e.g., 'example.com')
72 * @returns {Promise<object>} - Domain availability and pricing info
74async function searchDomain(domain) {
75 if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
76 throw new Error('Cloudflare Registrar credentials not configured');
80 const res = await axios.get(
81 `${CLOUDFLARE_BASE_URL}/accounts/${CLOUDFLARE_ACCOUNT_ID}/registrar/domains/${domain}`,
84 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
85 'Content-Type': 'application/json'
90 if (res.data.success) {
91 const result = res.data.result;
94 available: result.available,
95 price: result.current_year_cost || 0,
97 registrar: 'cloudflare',
98 canRegister: result.can_register,
99 supportedTlds: result.supported_tld
102 console.error('[Cloudflare Registrar] Search failed:', res.data.errors);
103 throw new Error('Domain search failed');
106 console.error('[Cloudflare Registrar] Error searching domain:', err.response?.data || err.message);
108 // If API not available, return placeholder
109 if (err.response?.status === 404 || err.response?.status === 403) {
115 registrar: 'cloudflare',
117 note: 'Cloudflare Registrar API not available - manual registration required'
126 * Register a domain on Cloudflare Registrar
127 * @param {object} params - Registration parameters
128 * @returns {Promise<object>} - Registration result
130async function registerDomain(params) {
139 if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
140 throw new Error('Cloudflare Registrar credentials not configured');
143 if (!registrant_contact) {
144 throw new Error('Registrant contact information is required');
148 const res = await axios.post(
149 `${CLOUDFLARE_BASE_URL}/accounts/${CLOUDFLARE_ACCOUNT_ID}/registrar/domains/${domain}`,
153 auto_renew: auto_renew,
154 registrant: registrant_contact
158 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
159 'Content-Type': 'application/json'
164 if (res.data.success) {
165 const result = res.data.result;
166 console.log(`[Cloudflare Registrar] Registered domain ${domain}`);
170 registration_date: result.created_on,
171 expiration_date: result.expires_on,
172 auto_renew: auto_renew,
174 registrar: 'cloudflare'
177 console.error('[Cloudflare Registrar] Registration failed:', res.data.errors);
178 throw new Error(res.data.errors?.[0]?.message || 'Domain registration failed');
181 console.error('[Cloudflare Registrar] Error registering domain:', err.response?.data || err.message);
183 // If API not available, throw descriptive error
184 if (err.response?.status === 404 || err.response?.status === 403) {
185 const error = new Error('Cloudflare Registrar API not available. Please register domain manually and add it to the system.');
186 error.code = 'API_NOT_AVAILABLE';
195 * Get domain details from Cloudflare Registrar
196 * @param {string} domain - Domain name
197 * @returns {Promise<object>} - Domain details
199async function getDomainDetails(domain) {
200 if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
201 throw new Error('Cloudflare Registrar credentials not configured');
205 const res = await axios.get(
206 `${CLOUDFLARE_BASE_URL}/accounts/${CLOUDFLARE_ACCOUNT_ID}/registrar/domains/${domain}`,
209 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
210 'Content-Type': 'application/json'
215 if (res.data.success) {
216 return res.data.result;
218 throw new Error('Failed to get domain details');
221 console.error('[Cloudflare Registrar] Error getting domain details:', err.response?.data || err.message);
227 * Get Cloudflare zone ID for a domain
228 * @param {string} domain - Domain name (e.g., 'example.com')
229 * @returns {Promise<string|null>} - Zone ID if found
231async function getZoneId(domain) {
232 if (!CLOUDFLARE_API_TOKEN) {
233 console.error('[Cloudflare] API token not configured');
238 const res = await axios.get(
239 `${CLOUDFLARE_BASE_URL}/zones?name=${domain}`,
242 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
243 'Content-Type': 'application/json'
248 if (res.data.success && res.data.result.length > 0) {
249 return res.data.result[0].id;
253 console.error('[Cloudflare] Error getting zone ID:', err.response?.data || err.message);
259 * List all zones in Cloudflare account
260 * @returns {Promise<Array>} - Array of zones
262async function listZones() {
263 if (!CLOUDFLARE_API_TOKEN) {
264 throw new Error('Cloudflare API token not configured');
268 const res = await axios.get(
269 `${CLOUDFLARE_BASE_URL}/zones`,
272 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
273 'Content-Type': 'application/json'
278 if (res.data.success) {
279 return res.data.result;
283 console.error('[Cloudflare] Error listing zones:', err.response?.data || err.message);
288// ============================================================================
289// DNS Zone & Record Management
290// ============================================================================
293 * Get zone details by domain name
294 * @param {string} domain - Domain name
295 * @returns {Promise<object | null>} - Zone object or null if not found
297async function getZone(domain) {
298 if (!CLOUDFLARE_API_TOKEN) {
299 throw new Error('Cloudflare API token not configured');
303 const res = await axios.get(
304 `${CLOUDFLARE_BASE_URL}/zones?name=${domain}`,
307 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
308 'Content-Type': 'application/json'
313 if (res.data.success && res.data.result.length > 0) {
314 return res.data.result[0];
318 console.error('[Cloudflare] Error getting zone:', err.response?.data || err.message);
324 * Create a DNS zone in Cloudflare
325 * @param {string} domain - Domain name (e.g., 'example.com')
326 * @param {object} options - Zone options
327 * @returns {Promise<object>} - Zone creation result
329async function createZone(domain, options = {}) {
330 if (!CLOUDFLARE_API_TOKEN) {
331 throw new Error('Cloudflare API token not configured');
334 if (!CLOUDFLARE_ACCOUNT_ID) {
335 console.warn('[Cloudflare] Account ID not set - zone will be created without account association');
341 jump_start: options.jump_start !== false // Auto-fetch DNS records from domain
344 // Add account if available
345 if (CLOUDFLARE_ACCOUNT_ID) {
346 payload.account = { id: CLOUDFLARE_ACCOUNT_ID };
349 const res = await axios.post(
350 `${CLOUDFLARE_BASE_URL}/zones`,
354 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
355 'Content-Type': 'application/json'
360 if (res.data.success) {
361 const zone = res.data.result;
362 console.log(`[Cloudflare] Created zone ${domain} (ID: ${zone.id})`);
367 nameservers: zone.name_servers,
368 created_on: zone.created_on
371 console.error('[Cloudflare] Zone creation failed:', res.data.errors);
372 throw new Error(res.data.errors?.[0]?.message || 'Zone creation failed');
375 // Handle zone already exists error
376 if (err.response?.data?.errors?.[0]?.code === 1061) {
377 console.warn(`[Cloudflare] Zone ${domain} already exists`);
378 // Try to get the existing zone
379 const zoneId = await getZoneId(domain);
381 const zones = await listZones();
382 const zone = zones.find(z => z.id === zoneId);
388 nameservers: zone.name_servers,
389 created_on: zone.created_on,
396 console.error('[Cloudflare] Error creating zone:', err.response?.data || err.message);
402 * Create a DNS record in Cloudflare
403 * @param {string} zoneId - Zone ID
404 * @param {object} record - DNS record details
405 * @returns {Promise<object>} - Created record
407async function createDNSRecord(zoneId, record) {
408 if (!CLOUDFLARE_API_TOKEN) {
409 throw new Error('Cloudflare API token not configured');
412 const { type, name, content, ttl = 3600, priority, proxied = false } = record;
414 if (!type || !name || !content) {
415 throw new Error('DNS record type, name, and content are required');
424 proxied: type === 'A' || type === 'AAAA' || type === 'CNAME' ? proxied : undefined
427 // Add priority for MX records
428 if (type === 'MX' && priority !== undefined) {
429 payload.priority = priority;
432 const res = await axios.post(
433 `${CLOUDFLARE_BASE_URL}/zones/${zoneId}/dns_records`,
437 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
438 'Content-Type': 'application/json'
443 if (res.data.success) {
444 console.log(`[Cloudflare] Created ${type} record ${name} -> ${content}`);
445 return res.data.result;
447 console.error('[Cloudflare] DNS record creation failed:', res.data.errors);
448 throw new Error(res.data.errors?.[0]?.message || 'DNS record creation failed');
451 // Handle duplicate record error
452 if (err.response?.data?.errors?.[0]?.code === 81057) {
453 console.warn(`[Cloudflare] DNS record ${type} ${name} already exists`);
454 const error = new Error('DNS record already exists');
455 error.code = 'RECORD_EXISTS';
459 console.error('[Cloudflare] Error creating DNS record:', err.response?.data || err.message);
465 * Update a DNS record in Cloudflare
466 * @param {string} zoneId - Zone ID
467 * @param {string} recordId - DNS record ID
468 * @param {object} record - Updated DNS record details
469 * @returns {Promise<object>} - Updated record
471async function updateDNSRecord(zoneId, recordId, record) {
472 if (!CLOUDFLARE_API_TOKEN) {
473 throw new Error('Cloudflare API token not configured');
476 const { type, name, content, ttl = 3600, priority, proxied = false } = record;
487 // Add priority for MX records
488 if (type === 'MX' && priority !== undefined) {
489 payload.priority = priority;
492 const res = await axios.put(
493 `${CLOUDFLARE_BASE_URL}/zones/${zoneId}/dns_records/${recordId}`,
497 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
498 'Content-Type': 'application/json'
503 if (res.data.success) {
504 console.log(`[Cloudflare] Updated ${type} record ${name}`);
505 return res.data.result;
507 console.error('[Cloudflare] DNS record update failed:', res.data.errors);
508 throw new Error(res.data.errors?.[0]?.message || 'DNS record update failed');
511 console.error('[Cloudflare] Error updating DNS record:', err.response?.data || err.message);
517 * Delete a DNS record from Cloudflare
518 * @param {string} zoneId - Zone ID
519 * @param {string} recordId - DNS record ID
520 * @returns {Promise<boolean>} - Success status
522async function deleteDNSRecord(zoneId, recordId) {
523 if (!CLOUDFLARE_API_TOKEN) {
524 throw new Error('Cloudflare API token not configured');
528 const res = await axios.delete(
529 `${CLOUDFLARE_BASE_URL}/zones/${zoneId}/dns_records/${recordId}`,
532 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
533 'Content-Type': 'application/json'
538 if (res.data.success) {
539 console.log(`[Cloudflare] Deleted DNS record ${recordId}`);
542 console.error('[Cloudflare] DNS record deletion failed:', res.data.errors);
543 throw new Error(res.data.errors?.[0]?.message || 'DNS record deletion failed');
546 console.error('[Cloudflare] Error deleting DNS record:', err.response?.data || err.message);
552 * List all DNS records for a zone
553 * @param {string} zoneId - Zone ID
554 * @returns {Promise<Array>} - Array of DNS records
556async function listDNSRecords(zoneId) {
557 if (!CLOUDFLARE_API_TOKEN) {
558 throw new Error('Cloudflare API token not configured');
562 const res = await axios.get(
563 `${CLOUDFLARE_BASE_URL}/zones/${zoneId}/dns_records?per_page=100`,
566 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
567 'Content-Type': 'application/json'
572 if (res.data.success) {
573 return res.data.result;
577 console.error('[Cloudflare] Error listing DNS records:', err.response?.data || err.message);
583 * Update nameservers for a domain (for registered domains only)
584 * @param {string} domain - Domain name
585 * @param {Array<string>} nameservers - Array of nameserver hostnames
586 * @returns {Promise<object>} - Update result
588async function updateNameservers(domain, nameservers) {
589 if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
590 throw new Error('Cloudflare Registrar credentials required to update nameservers');
594 const res = await axios.put(
595 `${CLOUDFLARE_BASE_URL}/accounts/${CLOUDFLARE_ACCOUNT_ID}/registrar/domains/${domain}`,
597 nameservers: nameservers
601 Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
602 'Content-Type': 'application/json'
607 if (res.data.success) {
608 console.log(`[Cloudflare] Updated nameservers for ${domain}`);
609 return res.data.result;
611 throw new Error(res.data.errors?.[0]?.message || 'Nameserver update failed');
614 console.error('[Cloudflare] Error updating nameservers:', err.response?.data || err.message);